diff options
author | Guillaume Melquiond <guillaume.melquiond@gmail.com> | 2006-12-29 13:43:24 +0000 |
---|---|---|
committer | Guillaume Melquiond <guillaume.melquiond@gmail.com> | 2006-12-29 13:43:24 +0000 |
commit | 291ad04d5b5c4ab08d85eadde116f968cd579b77 (patch) | |
tree | e4dced5715a5d9792cfdc0455a6b3ee6d3116079 /src/account-server | |
parent | 3d404e743105bb9168c89e3451cf35d7d59120b1 (diff) | |
download | manaserv-291ad04d5b5c4ab08d85eadde116f968cd579b77.tar.gz manaserv-291ad04d5b5c4ab08d85eadde116f968cd579b77.tar.bz2 manaserv-291ad04d5b5c4ab08d85eadde116f968cd579b77.tar.xz manaserv-291ad04d5b5c4ab08d85eadde116f968cd579b77.zip |
Physically split the server into one tmwserv-acount program (account +
chat + database) and multiple tmwserv-game programs (selected with
respect to the maps). Cleaned the repository by moving server-specific
source files into dedicated directories.
Diffstat (limited to 'src/account-server')
-rw-r--r-- | src/account-server/account.cpp | 218 | ||||
-rw-r--r-- | src/account-server/account.hpp | 232 | ||||
-rw-r--r-- | src/account-server/accountclient.cpp | 63 | ||||
-rw-r--r-- | src/account-server/accountclient.hpp | 95 | ||||
-rw-r--r-- | src/account-server/accounthandler.cpp | 707 | ||||
-rw-r--r-- | src/account-server/accounthandler.hpp | 82 | ||||
-rw-r--r-- | src/account-server/dalstorage.cpp | 742 | ||||
-rw-r--r-- | src/account-server/dalstorage.hpp | 177 | ||||
-rw-r--r-- | src/account-server/dalstoragesql.hpp | 334 | ||||
-rw-r--r-- | src/account-server/main-account.cpp | 327 | ||||
-rw-r--r-- | src/account-server/serverhandler.cpp | 115 | ||||
-rw-r--r-- | src/account-server/serverhandler.hpp | 90 | ||||
-rw-r--r-- | src/account-server/storage.cpp | 145 | ||||
-rw-r--r-- | src/account-server/storage.hpp | 290 |
14 files changed, 3617 insertions, 0 deletions
diff --git a/src/account-server/account.cpp b/src/account-server/account.cpp new file mode 100644 index 00000000..c827a92d --- /dev/null +++ b/src/account-server/account.cpp @@ -0,0 +1,218 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include <cassert> + +#include "account-server/account.hpp" +#include "utils/functors.h" + +/** + * Constructor with initial account info. + */ +Account::Account(const std::string& name, + const std::string& password, + const std::string& email, + int id) + : mID(id), + mName(name), + mPassword(password), + mEmail(email), + mCharacters(), + mLevel(AL_NORMAL) +{ + // NOOP +} + + +/** + * Constructor with initial account info. + */ +Account::Account(const std::string& name, + const std::string& password, + const std::string& email, + const Players& characters) + : mName(name), + mPassword(password), + mEmail(email), + mCharacters(characters), + mLevel(AL_NORMAL) +{ + // NOOP +} + + +/** + * Destructor. + */ +Account::~Account() +{ + // mCharacters is a list of smart pointers which will take care about + // deallocating the memory so nothing to deallocate here :) +} + + +/** + * Set the user name. + */ +void +Account::setName(const std::string& name) +{ + mName = name; +} + + +/** + * Get the user name. + */ +const std::string& +Account::getName(void) const +{ + return mName; +} + + +/** + * Set the user password. + */ +void +Account::setPassword(const std::string& password) +{ + mPassword = password; +} + + +/** + * Get the user password. + */ +const std::string +Account::getPassword(void) const +{ + return mPassword; +} + + +/** + * Set the user email address. + */ +void +Account::setEmail(const std::string& email) +{ + // should we check that the email address is valid first? + mEmail = email; +} + + +/** + * Get the user email address. + */ +const std::string& +Account::getEmail(void) const +{ + return mEmail; +} + + +/** + * Set the account level. + */ +void +Account::setLevel(AccountLevel level) +{ + mLevel = level; +} + + +/** + * Get the account level. + */ +AccountLevel +Account::getLevel(void) const +{ + return mLevel; +} + + +/** + * Set the characters. + */ +void +Account::setCharacters(const Players& characters) +{ + mCharacters = characters; +} + + +/** + * Add a new character. + */ +void +Account::addCharacter(PlayerPtr character) +{ + if (character.get() != 0) { + mCharacters.push_back(character); + } +} + +/** + * Remove a character. + */ +bool Account::delCharacter(std::string const &name) +{ + Players::iterator + end = mCharacters.end(), + it = std::find_if(mCharacters.begin(), end, + std::bind2nd(obj_name_is<PlayerPtr>(), name)); + + if (it == end) return false; + mCharacters.erase(it); + return true; +} + + +/** + * Get all the characters. + */ +Players &Account::getCharacters() +{ + return mCharacters; +} + + +/** + * Get a character by name. + */ +PlayerPtr Account::getCharacter(const std::string& name) +{ + Players::iterator + end = mCharacters.end(), + it = std::find_if(mCharacters.begin(), end, + std::bind2nd(obj_name_is<PlayerPtr>(), name)); + + if (it != end) return *it; + return PlayerPtr(); +} + +void Account::setID(int id) +{ + assert(mID < 0); + mID = id; +} diff --git a/src/account-server/account.hpp b/src/account-server/account.hpp new file mode 100644 index 00000000..e9d9e408 --- /dev/null +++ b/src/account-server/account.hpp @@ -0,0 +1,232 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#ifndef _TMWSERV_ACCOUNT_H_ +#define _TMWSERV_ACCOUNT_H_ + +#include <string> + +#include "player.h" + +/** + * Notes: + * - change from the previous implementation: this class does not encrypt + * passwords anymore and will just store the passwords as they are + * passed to setPassword(). + * - the encryption should and must be performed externally from this + * class or else we would end up with the password being encrypted many + * times (e.g setPassword(getPassword()) would encrypt the password + * twice or setPassword(encrypted_password_from_database) would also + * encrypt the password twice). + */ + + +/** + * A player account. + */ +class Account +{ + public: + /** + * Constructor with initial account info. + * + * @param name the user name. + * @param password the user password. + * @param email the user email. + */ + Account(const std::string& name, + const std::string& password, + const std::string& email, + int id = -1); + + + /** + * Constructor with initial account info. + * + * @param name the user name. + * @param password the user password. + * @param email the user email. + * @param characters the characters. + */ + Account(const std::string& name, + const std::string& password, + const std::string& email, + const Players& characters); + + + /** + * Destructor. + */ + ~Account(); + + + /** + * Set the user name. + * + * @param name the user name. + */ + void + setName(const std::string& name); + + + /** + * Get the user name. + * + * @return the user name. + */ + const std::string& + getName() const; + + + /** + * Set the user password. + * + * @param password the user password. + */ + void + setPassword(const std::string& password); + + + /** + * Get the user password. + * + * @return the user password. + */ + const std::string + getPassword(void) const; + + + /** + * Set the user email address. + * + * @param email the user email address. + */ + void + setEmail(const std::string& email); + + + /** + * Get the user email address. + * + * @return the user email address. + */ + const std::string& + getEmail(void) const; + + + /** + * Set the account level. + * + * @param level the new level. + */ + void + setLevel(AccountLevel level); + + + /** + * Get the account level. + * + * @return the account level. + */ + AccountLevel + getLevel() const; + + + /** + * Set the characters. + * + * @param characters a list of characters. + */ + void + setCharacters(const Players& characters); + + + /** + * Add a new character. + * + * @param character the new character. + */ + void + addCharacter(PlayerPtr character); + + /** + * Remove a character. + * + * @param name The character's name to delete. + */ + bool + delCharacter(std::string const &name); + + + /** + * Get all the characters. + * + * @return all the characters. + */ + Players& + getCharacters(); + + /** + * Get a character by name. + * + * @return the character if found, NULL otherwise. + */ + 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(); + Account(Account const &rhs); + Account &operator=(Account const &rhs); + + + private: + int mID; /**< unique id */ + std::string mName; /**< user name */ + std::string mPassword; /**< user password (encrypted) */ + std::string mEmail; /**< user email address */ + Players mCharacters; /**< player data */ + AccountLevel mLevel; /**< account level */ +}; + + +/** + * Type definition for a smart pointer to Account. + */ +typedef utils::CountedPtr<Account> AccountPtr; + + +#endif // _TMWSERV_ACCOUNT_H_ diff --git a/src/account-server/accountclient.cpp b/src/account-server/accountclient.cpp new file mode 100644 index 00000000..79d3cf0c --- /dev/null +++ b/src/account-server/accountclient.cpp @@ -0,0 +1,63 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include "account-server/account.hpp" +#include "account-server/accountclient.hpp" +#include "account-server/accounthandler.hpp" + +AccountClient::AccountClient(ENetPeer *peer): + NetComputer(peer), + mAccountPtr(NULL), + mCharacterPtr(NULL) +{ +} + +AccountClient::~AccountClient() +{ + unsetAccount(); +} + + +void AccountClient::setAccount(AccountPtr acc) +{ + unsetAccount(); + mAccountPtr = acc; +} + +void AccountClient::setCharacter(PlayerPtr ch) +{ + unsetCharacter(); + mCharacterPtr = ch; +} + +void AccountClient::unsetAccount() +{ + unsetCharacter(); + mAccountPtr = AccountPtr(NULL); +} + +void AccountClient::unsetCharacter() +{ + if (mCharacterPtr.get() == NULL) return; + mCharacterPtr = PlayerPtr(NULL); +} diff --git a/src/account-server/accountclient.hpp b/src/account-server/accountclient.hpp new file mode 100644 index 00000000..8d42e0e4 --- /dev/null +++ b/src/account-server/accountclient.hpp @@ -0,0 +1,95 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#ifndef _TMWSERV_ACCOUNTCLIENT_H_ +#define _TMWSERV_ACCOUNTCLIENT_H_ + +#include <enet/enet.h> + +#include "account-server/account.hpp" +#include "net/netcomputer.hpp" + +class AccountHandler; + +/** + * A connected computer that can have an account and character associated with + * it. + */ +class AccountClient : public NetComputer +{ + public: + /** + * Constructor. + */ + AccountClient(ENetPeer *peer); + + /** + * Destructor. + */ + ~AccountClient(); + + /** + * Set the account associated with the connection + */ + void + setAccount(AccountPtr acc); + + /** + * Unset the account associated with the connection + */ + void + unsetAccount(); + + /** + * Get account associated with the connection. + */ + AccountPtr + getAccount() const { return mAccountPtr; } + + /** + * Set the selected character associated with connection. + */ + void + setCharacter(PlayerPtr ch); + + /** + * Deselect the character associated with connection. + */ + void + unsetCharacter(); + + /** + * Get character associated with the connection + */ + PlayerPtr + getCharacter() const { return mCharacterPtr; } + + private: + /** Account associated with connection */ + AccountPtr mAccountPtr; + + /** Selected character */ + PlayerPtr mCharacterPtr; +}; + +#endif diff --git a/src/account-server/accounthandler.cpp b/src/account-server/accounthandler.cpp new file mode 100644 index 00000000..72a23818 --- /dev/null +++ b/src/account-server/accounthandler.cpp @@ -0,0 +1,707 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include "configuration.h" +#include "debug.h" +#include "point.h" +#include "account-server/accounthandler.hpp" +#include "account-server/account.hpp" +#include "account-server/accountclient.hpp" +#include "account-server/serverhandler.hpp" +#include "account-server/storage.hpp" +#include "chat-server/chathandler.hpp" +#include "net/connectionhandler.hpp" +#include "net/messagein.hpp" +#include "net/messageout.hpp" +#include "net/netcomputer.hpp" +#include "utils/logger.h" +#include "utils/stringfilter.h" + +bool +AccountHandler::startListen(enet_uint16 port) +{ + LOG_INFO("Account handler started:", 0); + return ConnectionHandler::startListen(port); +} + +NetComputer* +AccountHandler::computerConnected(ENetPeer *peer) +{ + return new AccountClient(peer); +} + +void +AccountHandler::computerDisconnected(NetComputer *comp) +{ + delete comp; +} + +/** + * Generic interface convention for getting a message and sending it to the + * correct subroutines. Account handler takes care of determining the + * current step in the account process, be it creation, setup, or login. + */ +void +AccountHandler::processMessage(NetComputer *comp, MessageIn &message) +{ + AccountClient &computer = *static_cast< AccountClient * >(comp); + + Storage &store = Storage::instance("tmw"); + +#if defined (SQLITE_SUPPORT) + // Reopen the db in this thread for sqlite, to avoid + // Library Call out of sequence problem due to thread safe. + store.setUser(config.getValue("dbuser", "")); + store.setPassword(config.getValue("dbpass", "")); + store.close(); + store.open(); +#endif + + MessageOut result; + + switch (message.getId()) + { + case PAMSG_LOGIN: + handleLoginMessage(computer, message); + break; + + case PAMSG_LOGOUT: + handleLogoutMessage(computer, message); + break; + + case PAMSG_REGISTER: + handleRegisterMessage(computer, message); + break; + + case PAMSG_UNREGISTER: + handleUnregisterMessage(computer, message); + break; + + case PAMSG_EMAIL_CHANGE: + { + result.writeShort(APMSG_EMAIL_CHANGE_RESPONSE); + + if (computer.getAccount().get() == NULL) { + result.writeByte(ERRMSG_NO_LOGIN); + LOG_INFO("Not logged in. Can't change your Account's Email.", 1); + break; + } + + std::string email = message.readString(); + if (!stringFilter->isEmailValid(email)) + { + result.writeByte(ERRMSG_INVALID_ARGUMENT); + LOG_INFO(email << ": Invalid format, cannot change Email for " << + computer.getAccount()->getName(), 1); + } + else if (stringFilter->findDoubleQuotes(email)) + { + result.writeByte(ERRMSG_INVALID_ARGUMENT); + LOG_INFO(email << ": has got double quotes in it.", 1); + } + else if (store.doesEmailAddressExist(email)) + { + result.writeByte(EMAILCHG_EXISTS_EMAIL); + LOG_INFO(email << ": New Email already exists.", 1); + } + else + { + computer.getAccount()->setEmail(email); + result.writeByte(ERRMSG_OK); + LOG_INFO(computer.getAccount()->getName() << ": Email changed to: " << + email, 1); + } + } + break; + + case PAMSG_EMAIL_GET: + { + result.writeShort(APMSG_EMAIL_GET_RESPONSE); + if (computer.getAccount().get() == NULL) { + result.writeByte(ERRMSG_NO_LOGIN); + LOG_INFO("Not logged in. Can't get your Account's current Email.", 1); + break; + } + else + { + result.writeByte(ERRMSG_OK); + result.writeString(computer.getAccount()->getEmail()); + } + } + break; + + case PAMSG_PASSWORD_CHANGE: + handlePasswordChangeMessage(computer, message); + break; + + case PAMSG_CHAR_CREATE: + handleCharacterCreateMessage(computer, message); + break; + + case PAMSG_CHAR_SELECT: + { + result.writeShort(APMSG_CHAR_SELECT_RESPONSE); + + if (computer.getAccount().get() == NULL) + { + result.writeByte(ERRMSG_NO_LOGIN); + LOG_INFO("Not logged in. Can't select a Character.", 1); + break; // not logged in + } + + unsigned char charNum = message.readByte(); + + Players &chars = computer.getAccount()->getCharacters(); + // Character ID = 0 to Number of Characters - 1. + if (charNum >= chars.size()) { + // invalid char selection + result.writeByte(ERRMSG_INVALID_ARGUMENT); + LOG_INFO("Character Selection: Selection out of ID range.", 1); + break; + } + + std::string address; + short port; + if (!serverHandler->getGameServerFromMap(chars[charNum]->getMapId(), address, port)) + { + result.writeByte(ERRMSG_FAILURE); + LOG_ERROR("Character Selection: No game server for the map.", 0); + break; + } + + // set character + computer.setCharacter(chars[charNum]); + PlayerPtr selectedChar = computer.getCharacter(); + result.writeByte(ERRMSG_OK); + + selectedChar->setDestination(selectedChar->getPosition()); + selectedChar->setSpeed(150); // TODO + + LOG_INFO(selectedChar->getName() + << " is trying to enter the servers.", 1); + + std::string magic_token(32, ' '); + for (int i = 0; i < 32; ++i) { + magic_token[i] = + 1 + (int) (127 * (rand() / (RAND_MAX + 1.0))); + } + result.writeString(magic_token, 32); + result.writeString(address); + result.writeShort(port); + // TODO: get correct address and port for the chat server + result.writeString(config.getValue("accountServerAddress", "localhost")); + result.writeShort(int(config.getValue("accountServerPort", DEFAULT_SERVER_PORT)) + 2); + + serverHandler->registerGameClient(magic_token, selectedChar); + registerChatClient(magic_token, selectedChar->getName(), + AL_NORMAL); + } + break; + + case PAMSG_CHAR_DELETE: + { + result.writeShort(APMSG_CHAR_DELETE_RESPONSE); + + if (computer.getAccount().get() == NULL) + { + result.writeByte(ERRMSG_NO_LOGIN); + LOG_INFO("Not logged in. Can't delete a Character.", 1); + break; // not logged in + } + + unsigned char charNum = message.readByte(); + + Players &chars = computer.getAccount()->getCharacters(); + // Character ID = 0 to Number of Characters - 1. + if (charNum >= chars.size()) { + // invalid char selection + result.writeByte(ERRMSG_INVALID_ARGUMENT); + LOG_INFO("Character Deletion : Selection out of ID range.", 1); + break; + } + + // Delete the character + // if the character to delete is the current character, get off of it in + // memory. + if ( computer.getCharacter().get() != NULL ) + { + if ( computer.getCharacter()->getName() == chars[charNum].get()->getName() ) + { + computer.unsetCharacter(); + } + } + + std::string deletedCharacter = chars[charNum].get()->getName(); + computer.getAccount()->delCharacter(deletedCharacter); + store.flush(computer.getAccount()); + LOG_INFO(deletedCharacter << ": Character deleted...", 1); + result.writeByte(ERRMSG_OK); + + } + break; + + default: + LOG_WARN("Invalid message type", 0); + result.writeShort(XXMSG_INVALID); + break; + } + + // return result + if (result.getLength() > 0) + computer.send(result); +} + +void +AccountHandler::handleLoginMessage(AccountClient &computer, MessageIn &msg) +{ + unsigned long clientVersion = msg.readLong(); + std::string username = msg.readString(); + std::string password = msg.readString(); + + LOG_INFO(username << " is trying to login.", 1); + + MessageOut reply(APMSG_LOGIN_RESPONSE); + + if (clientVersion < config.getValue("clientVersion", 0)) + { + LOG_INFO("Client has an insufficient version number to login.", 1); + reply.writeByte(LOGIN_INVALID_VERSION); + } + if (stringFilter->findDoubleQuotes(username)) + { + LOG_INFO(username << ": has got double quotes in it.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + if (computer.getAccount().get() != NULL) { + LOG_INFO("Already logged in as " << computer.getAccount()->getName() + << ".", 1); + LOG_INFO("Please logout first.", 1); + reply.writeByte(ERRMSG_FAILURE); + } + if (getClientNumber() >= MAX_CLIENTS ) + { + LOG_INFO("Client couldn't login. Already has " << MAX_CLIENTS + << " logged in.", 1); + reply.writeByte(LOGIN_SERVER_FULL); + } + else + { + // Check if the account exists + Storage &store = Storage::instance("tmw"); + AccountPtr acc = store.getAccount(username); + + if (!acc.get() || acc->getPassword() != password) + { + LOG_INFO(username << ": Account does not exist or the password is " + "invalid.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else + { + LOG_INFO("Login OK by " << username, 1); + + // Associate account with connection + computer.setAccount(acc); + + reply.writeByte(ERRMSG_OK); + computer.send(reply); + + // Return information about available characters + Players &chars = computer.getAccount()->getCharacters(); + + LOG_INFO(username << "'s account has " << chars.size() + << " character(s).", 1); + + // Send characters list + for (unsigned int i = 0; i < chars.size(); i++) + { + MessageOut charInfo(APMSG_CHAR_INFO); + charInfo.writeByte(i); // Slot + charInfo.writeString(chars[i]->getName()); + charInfo.writeByte((unsigned char) chars[i]->getGender()); + charInfo.writeByte(chars[i]->getHairStyle()); + charInfo.writeByte(chars[i]->getHairColor()); + charInfo.writeByte(chars[i]->getLevel()); + charInfo.writeShort(chars[i]->getMoney()); + for (int j = 0; j < NB_RSTAT; ++j) + charInfo.writeShort(chars[i]->getRawStat(j)); + computer.send(charInfo); + } + return; + } + } + + computer.send(reply); +} + +void +AccountHandler::handleLogoutMessage(AccountClient &computer, MessageIn &msg) +{ + MessageOut reply(APMSG_LOGOUT_RESPONSE); + + if (computer.getAccount().get() == NULL) + { + LOG_INFO("Can't logout. Not even logged in.", 1); + reply.writeByte(ERRMSG_NO_LOGIN); + } + else + { + LOG_INFO(computer.getAccount()->getName() << " logged out.", 1); + computer.unsetAccount(); + reply.writeByte(ERRMSG_OK); + } + + computer.send(reply); +} + +void +AccountHandler::handleRegisterMessage(AccountClient &computer, MessageIn &msg) +{ + unsigned long clientVersion = msg.readLong(); + std::string username = msg.readString(); + std::string password = msg.readString(); + std::string email = msg.readString(); + + LOG_INFO(username << " is trying to register.", 1); + + MessageOut reply(APMSG_REGISTER_RESPONSE); + + if (clientVersion < config.getValue("clientVersion", 0)) + { + LOG_INFO("Client has an unsufficient version number to login.", 1); + reply.writeByte(REGISTER_INVALID_VERSION); + } + else if (stringFilter->findDoubleQuotes(username)) + { + LOG_INFO(username << ": has got double quotes in it.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if (stringFilter->findDoubleQuotes(email)) + { + LOG_INFO(email << ": has got double quotes in it.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if ((username.length() < MIN_LOGIN_LENGTH) || + (username.length() > MAX_LOGIN_LENGTH)) + { + LOG_INFO(username << ": Username too short or too long.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if ((password.length() < MIN_PASSWORD_LENGTH) || + (password.length() > MAX_PASSWORD_LENGTH)) + { + LOG_INFO(email << ": Password too short or too long.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if (!stringFilter->isEmailValid(email)) + { + LOG_INFO(email << ": Email Invalid, only a@b.c format is accepted.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + // Checking if the Name is slang's free. + else if (!stringFilter->filterContent(username)) + { + LOG_INFO(username << ": has got bad words in it.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else + { + Storage &store = Storage::instance("tmw"); + AccountPtr accPtr = store.getAccount(username); + + // Check whether the account already exists. + if (accPtr.get()) + { + LOG_INFO(username << ": Username already exists.", 1); + reply.writeByte(REGISTER_EXISTS_USERNAME); + } + // Find out whether the email is already in use. + else if (store.doesEmailAddressExist(email)) + { + LOG_INFO(email << ": Email already exists.", 1); + reply.writeByte(REGISTER_EXISTS_EMAIL); + } + else + { + AccountPtr acc(new Account(username, password, email)); + store.addAccount(acc); + LOG_INFO(username << ": Account registered.", 1); + + reply.writeByte(ERRMSG_OK); + } + } + + computer.send(reply); +} + +void +AccountHandler::handleUnregisterMessage(AccountClient &computer, + MessageIn &msg) +{ + std::string username = msg.readString(); + std::string password = msg.readString(); + + LOG_INFO(username << " wants to be deleted from our accounts.", 1); + + MessageOut reply(APMSG_UNREGISTER_RESPONSE); + + if (stringFilter->findDoubleQuotes(username)) + { + LOG_INFO(username << ": has got double quotes in it.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else + { + // See if the account exists + Storage &store = Storage::instance("tmw"); + AccountPtr accPtr = store.getAccount(username); + + if (!accPtr.get() || accPtr->getPassword() != password) + { + LOG_INFO("Account does not exist or bad password for " + << username << ".", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else + { + // If the account to delete is the current account we're logged in. + // Get out of it in memory. + if (computer.getAccount().get() != NULL ) + { + if (computer.getAccount()->getName() == username) + { + computer.unsetAccount(); + } + } + + // Delete account and associated characters + LOG_INFO("Farewell " << username << " ...", 1); + store.delAccount(accPtr); + reply.writeByte(ERRMSG_OK); + } + } + + computer.send(reply); +} + +void +AccountHandler::handlePasswordChangeMessage(AccountClient &computer, + MessageIn &msg) +{ + std::string oldPassword = msg.readString(); + std::string newPassword = msg.readString(); + + MessageOut reply(APMSG_PASSWORD_CHANGE_RESPONSE); + + if (computer.getAccount().get() == NULL) + { + LOG_INFO("Not logged in. Can't change your Account's Password.", 1); + reply.writeByte(ERRMSG_NO_LOGIN); + } + else if (newPassword.length() < MIN_PASSWORD_LENGTH || + newPassword.length() > MAX_PASSWORD_LENGTH) + { + LOG_INFO(computer.getAccount()->getName() << + ": New password too long or too short.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if (stringFilter->findDoubleQuotes(newPassword)) + { + LOG_INFO(newPassword << ": has got double quotes in it.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if (oldPassword != computer.getAccount()->getPassword()) + { + LOG_INFO(computer.getAccount()->getName() << + ": Old password is wrong.", 1); + reply.writeByte(ERRMSG_FAILURE); + } + else + { + LOG_INFO(computer.getAccount()->getName() << + ": The password was changed.", 1); + computer.getAccount()->setPassword(newPassword); + reply.writeByte(ERRMSG_OK); + } + + computer.send(reply); +} + +void +AccountHandler::handleCharacterCreateMessage(AccountClient &computer, + MessageIn &msg) +{ + std::string name = msg.readString(); + char hairStyle = msg.readByte(); + char hairColor = msg.readByte(); + Gender gender = (Gender) msg.readByte(); + + MessageOut reply(APMSG_CHAR_CREATE_RESPONSE); + + if (computer.getAccount().get() == NULL) { + LOG_INFO("Not logged in. Can't create a Character.", 1); + reply.writeByte(ERRMSG_NO_LOGIN); + } + else if (!stringFilter->filterContent(name)) + { + LOG_INFO(name << ": Character has got bad words in it.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if (stringFilter->findDoubleQuotes(name)) + { + LOG_INFO(name << ": has got double quotes in it.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else if ((hairStyle < 0) || (hairStyle > (signed) MAX_HAIRSTYLE_VALUE)) + { + LOG_INFO(name << ": Character's hair Style is invalid.", 1); + reply.writeByte(CREATE_INVALID_HAIRSTYLE); + } + else if ((hairColor < 0) || (hairColor > (signed) MAX_HAIRCOLOR_VALUE)) + { + LOG_INFO(name << ": Character's hair Color is invalid.", 1); + reply.writeByte(CREATE_INVALID_HAIRCOLOR); + } + else if ((gender < 0) || (gender > (signed) MAX_GENDER_VALUE)) + { + LOG_INFO(name << ": Character's gender is invalid.", 1); + reply.writeByte(CREATE_INVALID_GENDER); + } + else if ((name.length() < MIN_CHARACTER_LENGTH) || + (name.length() > MAX_CHARACTER_LENGTH)) + { + LOG_INFO(name << ": Character's name too short or too long.", 1); + reply.writeByte(ERRMSG_INVALID_ARGUMENT); + } + else + { + Storage &store = Storage::instance("tmw"); + if (store.doesCharacterNameExist(name)) + { + LOG_INFO(name << ": Character's name already exists.", 1); + reply.writeByte(CREATE_EXISTS_NAME); + computer.send(reply); + return; + } + + // A player shouldn't have more than MAX_OF_CHARACTERS characters. + Players &chars = computer.getAccount()->getCharacters(); + if (chars.size() >= MAX_OF_CHARACTERS) + { + LOG_INFO("Already has " << chars.size() + << " characters. Can't create another Character.", 1); + reply.writeByte(CREATE_TOO_MUCH_CHARACTERS); + computer.send(reply); + return; + } + + // LATER_ON: Add race, face and maybe special attributes. + + // Customization of player's stats... + RawStatistics rawStats; + for (int i = 0; i < NB_RSTAT; ++i) + rawStats.stats[i] = msg.readShort(); + + // We see if the difference between the lowest stat and the highest + // isn't too big. + unsigned short lowestStat = 0; + unsigned short highestStat = 0; + unsigned int totalStats = 0; + bool validNonZeroRawStats = true; + for (int i = 0; i < NB_RSTAT; ++i) + { + unsigned short stat = rawStats.stats[i]; + // For good total stat check. + totalStats = totalStats + stat; + + // For checking if all stats are at least > 0 + if (stat <= 0) validNonZeroRawStats = false; + if (lowestStat == 0 || lowestStat > stat) lowestStat = stat; + if (highestStat == 0 || highestStat < stat) highestStat = stat; + } + + if (totalStats > POINTS_TO_DISTRIBUTES_AT_LVL1) + { + LOG_INFO(name << ": Character's stats are too high to be of " + "level 1.", 1); + reply.writeByte(CREATE_RAW_STATS_TOO_HIGH); + } + else if (totalStats < POINTS_TO_DISTRIBUTES_AT_LVL1) + { + LOG_INFO(name << ": Character's stats are too low to be of " + "level 1.", 1); + reply.writeByte(CREATE_RAW_STATS_TOO_LOW); + } + else if ((highestStat - lowestStat) > (signed) MAX_DIFF_BETWEEN_STATS) + { + LOG_INFO(name << ": Character's stats difference is too high to " + "be accepted.", 1); + reply.writeByte(CREATE_RAW_STATS_INVALID_DIFF); + } + else if (!validNonZeroRawStats) + { + LOG_INFO(name << ": One stat is equal to zero.", 1); + reply.writeByte(CREATE_RAW_STATS_EQUAL_TO_ZERO); + } + else + { + PlayerPtr newCharacter(new Player(name)); + for (int i = 0; i < NB_RSTAT; ++i) + newCharacter->setRawStat(i, rawStats.stats[i]); + newCharacter->setMoney(0); + newCharacter->setLevel(1); + newCharacter->setGender(gender); + newCharacter->setHairStyle(hairStyle); + newCharacter->setHairColor(hairColor); + newCharacter->setMapId((int) config.getValue("defaultMap", 1)); + Point startingPos = { (int) config.getValue("startX", 0), + (int) config.getValue("startY", 0) }; + newCharacter->setPosition(startingPos); + computer.getAccount()->addCharacter(newCharacter); + + LOG_INFO("Character " << name << " was created for " + << computer.getAccount()->getName() << "'s account.", 1); + + store.flush(computer.getAccount()); // flush changes + reply.writeByte(ERRMSG_OK); + computer.send(reply); + + // Send new characters infos back to client + MessageOut charInfo(APMSG_CHAR_INFO); + int slot = chars.size() - 1; + charInfo.writeByte(slot); + charInfo.writeString(chars[slot]->getName()); + charInfo.writeByte((unsigned char) chars[slot]->getGender()); + charInfo.writeByte(chars[slot]->getHairStyle()); + charInfo.writeByte(chars[slot]->getHairColor()); + charInfo.writeByte(chars[slot]->getLevel()); + charInfo.writeShort(chars[slot]->getMoney()); + for (int j = 0; j < NB_RSTAT; ++j) + charInfo.writeShort(chars[slot]->getRawStat(j)); + computer.send(charInfo); + return; + } + } + + computer.send(reply); +} diff --git a/src/account-server/accounthandler.hpp b/src/account-server/accounthandler.hpp new file mode 100644 index 00000000..2d75fbf5 --- /dev/null +++ b/src/account-server/accounthandler.hpp @@ -0,0 +1,82 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#ifndef _TMWSERV_ACCOUNTHANDLER_H_ +#define _TMWSERV_ACCOUNTHANDLER_H_ + +#include "net/connectionhandler.hpp" + +class AccountClient; + +/** + * Manages the data stored in user accounts and provides a reliable interface + * for working with an account. The account handler class can be used as a link + * to a working account handle, and can be assigned to a user persistently as + * an interface between the computer and account. (Messages from the user can + * be traced to this account through the NetComputer structure, then processed + * here with the persistent stored data). + */ +class AccountHandler : public ConnectionHandler +{ + public: + /** + * Start the handler + */ + bool + startListen(enet_uint16 port); + + protected: + /** + * Process account related messages. + */ + void + processMessage(NetComputer *computer, MessageIn &message); + + NetComputer* + computerConnected(ENetPeer *peer); + + void + computerDisconnected(NetComputer *comp); + + // --- message handling --- + + void + handleLoginMessage(AccountClient &computer, MessageIn &msg); + + void + handleLogoutMessage(AccountClient &computer, MessageIn &msg); + + void + handleRegisterMessage(AccountClient &computer, MessageIn &msg); + + void + handleUnregisterMessage(AccountClient &computer, MessageIn &msg); + + void + handlePasswordChangeMessage(AccountClient &computer, MessageIn &msg); + + void + handleCharacterCreateMessage(AccountClient &computer, MessageIn &msg); +}; + +#endif diff --git a/src/account-server/dalstorage.cpp b/src/account-server/dalstorage.cpp new file mode 100644 index 00000000..95d6c019 --- /dev/null +++ b/src/account-server/dalstorage.cpp @@ -0,0 +1,742 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include <cassert> + +#include "configuration.h" +#include "point.h" +#include "account-server/dalstorage.hpp" +#include "account-server/dalstoragesql.hpp" +#include "dal/dalexcept.h" +#include "dal/dataproviderfactory.h" +#include "utils/cipher.h" +#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<unsigned, AccountPtr> const &elem) const + { return elem.second->getName() == mName; } + + private: + std::string mName; /**< the name to look for */ +}; + + +/** + * Constructor. + */ +DALStorage::DALStorage() + : mDb(dal::DataProviderFactory::createDataProvider()) +{ + // the connection to the database will be made on the first request + // to the database. +} + + +/** + * Destructor. + */ +DALStorage::~DALStorage() + throw() +{ + if (mDb->isConnected()) { + close(); + } + + // mAccounts and mCharacters contain smart pointers that will deallocate + // the memory so nothing else to do here :) +} + + +/** + * Connect to the database and initialize it if necessary. + */ +void +DALStorage::open(void) +{ + // Do nothing if already connected. + if (mDb->isConnected()) { + return; + } + + using namespace dal; + + static bool dbFileShown = false; + std::string dbFile(getName()); + try { + // open a connection to the database. +#if defined (MYSQL_SUPPORT) || defined (POSTGRESQL_SUPPORT) + mDb->connect(getName(), getUser(), getPassword()); + if (!dbFileShown) + { + LOG_INFO("Using " << dbFile << " as Database Name.", 0); + dbFileShown = true; + } +#elif defined (SQLITE_SUPPORT) + // create the database file name. + dbFile += ".db"; + mDb->connect(dbFile, "", ""); + if (!dbFileShown) + { + LOG_INFO("SQLite uses ./" << dbFile << " as DB.", 0); + dbFileShown = true; + } +#endif + + // ensure that the required tables are created. + // + // strategy1: find a way to obtain the list of tables from the + // underlying database and create the tables that are + // missing. + // + // strategy2: try to create the tables and check the exceptions + // thrown. + // + // comments: + // - strategy1 is easy to achieve if we are using MysQL as + // executing the request "show tables;" returns the list of + // tables. However, there is not such a query for SQLite3. + // When using SQLite3 from the interactive shell or the + // command line, the command ".tables" returns the list of + // tables but sqlite3_exec() does not validate this statement + // and fails. + // The cost of this strategy is: + // (num. tables to create + 1) queries at most and + // 1 at minimum. + // + // - strategy2 will work with probably most databases. + // The cost of this strategy is: + // (num. tables to create) queries. + + // we will stick with strategy2 for the moment as we are focusing + // on SQLite. + + // FIXME: The tables should be checked/created at startup in order to avoid + // a DbSqlQueryExecFailure assert on sqlite while registering. + // Also, this would initialize connection to the database earlier in memory. + + createTable(ACCOUNTS_TBL_NAME, SQL_ACCOUNTS_TABLE); + createTable(CHARACTERS_TBL_NAME, SQL_CHARACTERS_TABLE); + createTable(ITEMS_TBL_NAME, SQL_ITEMS_TABLE); + createTable(WORLD_ITEMS_TBL_NAME, SQL_WORLD_ITEMS_TABLE); + createTable(INVENTORIES_TBL_NAME, SQL_INVENTORIES_TABLE); + createTable(CHANNELS_TBL_NAME, SQL_CHANNELS_TABLE); + } + catch (const DbConnectionFailure& e) { + LOG_ERROR("unable to connect to the database: " << e.what(), 0); + } + catch (const DbSqlQueryExecFailure& e) { + LOG_ERROR("SQL query failure: " << e.what(), 0); + } + + mIsOpen = mDb->isConnected(); +} + + +/** + * Disconnect from the database. + */ +void +DALStorage::close(void) +{ + mDb->disconnect(); + mIsOpen = mDb->isConnected(); +} + + +/** + * Get an account by user name. + */ +AccountPtr +DALStorage::getAccount(const std::string& userName) +{ + // connect to the database (if not connected yet). + open(); + + // look for the account in the list 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; + + // the account was not in the list, look for it in the database. + try { + std::string sql("select * from "); + sql += ACCOUNTS_TBL_NAME; + sql += " where username = \""; + sql += userName; + sql += "\";"; + const RecordSet& accountInfo = mDb->execSql(sql); + + // if the account is not even in the database then + // we have no choice but to return nothing. + if (accountInfo.isEmpty()) { + return AccountPtr(NULL); + } + + // specialize the string_to functor to convert + // a string to an unsigned int. + string_to<unsigned short> 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), id)); + + // specialize the string_to functor to convert + // a string to an unsigned short. + string_to<unsigned short> toUshort; + + mAccounts.insert(std::make_pair(id, account)); + + // load the characters associated with the account. + sql = "select * from "; + sql += CHARACTERS_TBL_NAME; + sql += " where user_id = '"; + sql += accountInfo(0, 0); + sql += "';"; + const RecordSet& charInfo = mDb->execSql(sql); + + if (!charInfo.isEmpty()) { + Players players; + + LOG_INFO(userName << "'s account has " << charInfo.rows() + << " character(s) in database.", 1); + + // As the recordset functions are set to be able to get one + // recordset at a time, we store charInfo in a temp array of + // strings. To avoid the problem where values of charInfo were + // erased by the values of mapInfo. + std::string strCharInfo[charInfo.rows()][charInfo.cols()]; + for (unsigned int i = 0; i < charInfo.rows(); ++i) + { + for (unsigned int j = 0; j < charInfo.cols(); ++j) + { + strCharInfo[i][j] = charInfo(i,j); + } + } + unsigned int charRows = charInfo.rows(); + + for (unsigned int k = 0; k < charRows; ++k) { + PlayerPtr player(new Player(strCharInfo[k][2], toUint(strCharInfo[k][0]))); + player->setGender((Gender) toUshort(strCharInfo[k][3])); + player->setHairStyle(toUshort(strCharInfo[k][4])); + player->setHairColor(toUshort(strCharInfo[k][5])); + player->setLevel(toUshort(strCharInfo[k][6])); + player->setMoney(toUint(strCharInfo[k][7])); + Point pos = { toUshort(strCharInfo[k][8]), + toUshort(strCharInfo[k][9]) }; + player->setPosition(pos); + for (int i = 0; i < NB_RSTAT; ++i) + player->setRawStat(i, toUshort(strCharInfo[k][11 + i])); + + unsigned int mapId = toUint(strCharInfo[k][10]); + if ( mapId > 0 ) + { + player->setMapId(mapId); + } + else + { + // Set player to default map and one of the default location + // Default map is to be 1, as not found return value will be 0. + player->setMapId((int)config.getValue("defaultMap", 1)); + } + + mCharacters.push_back(player); + players.push_back(player); + } // End of for each characters + + account->setCharacters(players); + } // End if there are characters. + + return account; + } + catch (const DbSqlQueryExecFailure& e) { + return AccountPtr(NULL); // TODO: Throw exception here + } +} + + +/** + * Return the list of all Emails addresses. + */ +std::list<std::string> +DALStorage::getEmailList() +{ + // If not opened already + open(); + + std::list <std::string> emailList; + + try { + std::string sql("select email from "); + sql += ACCOUNTS_TBL_NAME; + sql += ";"; + const dal::RecordSet& accountInfo = mDb->execSql(sql); + + // if the account is not even in the database then + // we have no choice but to return nothing. + if (accountInfo.isEmpty()) { + return emailList; + } + for (unsigned int i = 0; i < accountInfo.rows(); i++) + { + // We add all these addresses to the list + emailList.push_front(accountInfo(i, 0)); + } + } + catch (const dal::DbSqlQueryExecFailure& e) { + // TODO: throw an exception. + LOG_ERROR("SQL query failure: " << e.what(), 0); + } + + return emailList; +} + +/** + * Tells if the email address already exists + * @return true if the email address exists. + */ +bool DALStorage::doesEmailAddressExist(std::string const &email) +{ + // If not opened already + open(); + + try { + std::ostringstream sql; + sql << "select count(email) from " << ACCOUNTS_TBL_NAME + << " where upper(email) = upper(\"" << email << "\");"; + dal::RecordSet const &accountInfo = mDb->execSql(sql.str()); + + std::istringstream ssStream(accountInfo(0, 0)); + unsigned int iReturn = 1; + ssStream >> iReturn; + return iReturn != 0; + } catch (std::exception const &e) { + // TODO: throw an exception. + LOG_ERROR("SQL query failure: " << e.what(), 0); + } + + return true; +} + +/** + * Tells if the character's name already exists + * @return true if character's name exists. + */ +bool DALStorage::doesCharacterNameExist(const std::string& name) +{ + // If not opened already + open(); + + try { + std::ostringstream sql; + sql << "select count(name) from " << CHARACTERS_TBL_NAME + << " where name = \"" << name << "\";"; + dal::RecordSet const &accountInfo = mDb->execSql(sql.str()); + + std::istringstream ssStream(accountInfo(0, 0)); + int iReturn = 1; + ssStream >> iReturn; + return iReturn != 0; + } catch (std::exception const &e) { + // TODO: throw an exception. + LOG_ERROR("SQL query failure: " << e.what(), 0); + } + + return true; +} + +std::map<short, ChatChannel> +DALStorage::getChannelList() +{ + // If not opened already + open(); + + // specialize the string_to functor to convert + // a string to a short. + string_to<short> toShort; + + // The formatted datas + std::map<short, ChatChannel> channels; + + try { + std::stringstream sql; + sql << "select id, name, announcement, password from "; + sql << CHANNELS_TBL_NAME; + sql << ";"; + + const dal::RecordSet& channelInfo = mDb->execSql(sql.str()); + + // If the map return is empty then we have no choice but to return false. + if (channelInfo.isEmpty()) { + return channels; + } + + for ( unsigned int i = 0; i < channelInfo.rows(); ++i) + { + channels.insert(std::make_pair(toShort(channelInfo(i,0)), + ChatChannel(channelInfo(i,1), + channelInfo(i,2), + channelInfo(i,3)))); + + LOG_DEBUG("Channel (" << channelInfo(i,0) << ") loaded: " << channelInfo(i,1) + << ": " << channelInfo(i,2), 5); + } + + return channels; + } + catch (const dal::DbSqlQueryExecFailure& e) { + // TODO: throw an exception. + LOG_ERROR("SQL query failure: " << e.what(), 0); + } + + return channels; +} + +void +DALStorage::updateChannels(std::map<short, ChatChannel>& channelList) +{ +#if defined (SQLITE_SUPPORT) + // Reopen the db in this thread for sqlite, to avoid + // Library Call out of sequence problem due to thread safe. + close(); +#endif + open(); + + try { + // Empties the table + std::stringstream sql; + sql << "delete from " + << CHANNELS_TBL_NAME + << ";"; + + mDb->execSql(sql.str()); + + for (std::map<short, ChatChannel>::iterator i = channelList.begin(); + i != channelList.end();) + { + // insert registered channel if id < MAX_PUBLIC_CHANNELS_RANGE; + if ( i->first < (signed)MAX_PUBLIC_CHANNELS_RANGE ) + { + if (i->second.getName() != "") + { + sql.str(""); + sql << "insert into " + << CHANNELS_TBL_NAME + << " (id, name, announcement, password)" + << " values (" + << i->first << ", \"" + << i->second.getName() << "\", \"" + << i->second.getAnnouncement() << "\", \"" + << i->second.getPassword() << "\");"; + + LOG_DEBUG("Channel (" << i->first << ") saved: " << i->second.getName() + << ": " << i->second.getAnnouncement(), 5); + } + + mDb->execSql(sql.str()); + } + ++i; + } + + } + catch (const dal::DbSqlQueryExecFailure& e) { + // TODO: throw an exception. + LOG_ERROR("SQL query failure: " << e.what(), 0); + } +} + + +/** + * Create the specified table. + */ +void +DALStorage::createTable(const std::string& tblName, + const std::string& sql) +{ + try { + mDb->execSql(sql); + } + catch (const dal::DbSqlQueryExecFailure& e) { + // error message to check against. +#if defined (MYSQL_SUPPORT) + std::string alreadyExists("Table '"); + alreadyExists += tblName; + alreadyExists += "' already exists"; +#elif defined (POSTGRESQL_SUPPORT) + std::string alreadyExists("table "); + alreadyExists += tblName; + alreadyExists += " already exists"; +#else // SQLITE_SUPPORT + std::string alreadyExists("table "); + alreadyExists += tblName; + alreadyExists += " already exists"; +#endif + + const std::string msg(e.what()); + + // oops, another problem occurred. + if (msg != alreadyExists) { + // rethrow to let other error handlers manage the problem. + throw; + } + } +} + + +/** + * Add an account to the database. + */ +void DALStorage::addAccount(AccountPtr const &account) +{ + assert(account->getCharacters().size() == 0); + + using namespace dal; + + // TODO: we should start a transaction here so that in case of problem + // the lost of data would be minimized. + + // insert the account. + std::ostringstream sql1; + sql1 << "insert into " << ACCOUNTS_TBL_NAME + << " (username, password, email, level, banned)" + << " values (\"" + << account->getName() << "\", \"" + << account->getPassword() << "\", \"" + << account->getEmail() << "\", " + << account->getLevel() << ", 0);"; + mDb->execSql(sql1.str()); + + // get the account id. + std::ostringstream sql2; + sql2 << "select id from " << ACCOUNTS_TBL_NAME + << " where username = \"" << account->getName() << "\";"; + const RecordSet& accountInfo = mDb->execSql(sql2.str()); + string_to<unsigned int> toUint; + 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::flush(AccountPtr const &account) +{ + assert(account->getID() >= 0); + + using namespace dal; + + // TODO: we should start a transaction here so that in case of problem + // the loss of data would be minimized. + + // update the account. + std::ostringstream sql1; + sql1 << "update " << ACCOUNTS_TBL_NAME + << " set username = \"" << account->getName() << "\", " + << "password = \"" << account->getPassword() << "\", " + << "email = \"" << account->getEmail() << "\", " + << "level = '" << account->getLevel() << "' " + << "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. + for (Players::const_iterator it = characters.begin(), + it_end = characters.end(); it != it_end; ++it) { + + std::ostringstream sql3; + if ((*it)->getDatabaseID() < 0) { + // insert the character + sql3 << "insert into " << CHARACTERS_TBL_NAME + << " (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() << ", " + << (int)(*it)->getHairColor() << ", " + << (int)(*it)->getLevel() << ", " + << (*it)->getMoney() << ", " + << (*it)->getPosition().x << ", " + << (*it)->getPosition().y << ", " + << (*it)->getMapId() << ", " + << (*it)->getRawStat(STAT_STRENGTH) << ", " + << (*it)->getRawStat(STAT_AGILITY) << ", " + << (*it)->getRawStat(STAT_VITALITY) << ", " + << (*it)->getRawStat(STAT_INTELLIGENCE) << ", " + << (*it)->getRawStat(STAT_DEXTERITY) << ", " + << (*it)->getRawStat(STAT_LUCK) << ");"; + + // get the character id + std::ostringstream sql2; + sql2 << "select id from " << CHARACTERS_TBL_NAME + << " where name = \"" << (*it)->getName() << "\";"; + RecordSet const &charInfo = mDb->execSql(sql2.str()); + if (charInfo.isEmpty()) { + // FIXME: this does not make any sense to me -- silene + (*it)->setDatabaseID(1); + } + else + { + string_to<unsigned int> toUint; + (*it)->setDatabaseID(toUint(charInfo(0, 0))); + } + } else { + sql3 << "update " << CHARACTERS_TBL_NAME + << " set name = \"" << (*it)->getName() << "\", " + << " gender = " << (*it)->getGender() << ", " + << " hair_style = " << (int)(*it)->getHairStyle() << ", " + << " hair_color = " << (int)(*it)->getHairColor() << ", " + << " level = " << (int)(*it)->getLevel() << ", " + << " money = " << (*it)->getMoney() << ", " + << " x = " << (*it)->getPosition().x << ", " + << " y = " << (*it)->getPosition().y << ", " + << " map_id = " << (*it)->getMapId() << ", " + << " str = " << (*it)->getRawStat(STAT_STRENGTH) << ", " + << " agi = " << (*it)->getRawStat(STAT_AGILITY) << ", " + << " vit = " << (*it)->getRawStat(STAT_VITALITY) << ", " +#if defined(MYSQL_SUPPORT) || defined(POSTGRESQL_SUPPORT) + << " `int` = " << (*it)->getRawStat(STAT_INTELLIGENCE) << ", " +#else + << " int = " << (*it)->getRawStat(STAT_INTELLIGENCE) << ", " +#endif + << " dex = " << (*it)->getRawStat(STAT_DEXTERITY) << ", " + << " luck = " << (*it)->getRawStat(STAT_LUCK) + << " where id = " << (*it)->getDatabaseID() << ";"; + } + mDb->execSql(sql3.str()); + + // TODO: inventories. + } + + // Existing characters in memory have been inserted or updated in database. + // Now, let's remove those who are no more in memory from database. + + // specialize the string_to functor to convert + // a string to an unsigned int. + string_to<unsigned short> toUint; + + std::ostringstream sql4; + sql4 << "select name, id from " << CHARACTERS_TBL_NAME + << " where user_id = '" << account->getID() << "';"; + const RecordSet& charInMemInfo = mDb->execSql(sql4.str()); + + // We compare chars from memory and those existing in db, + // And delete those not in mem but existing in db. + bool charFound; + for ( unsigned int i = 0; i < charInMemInfo.rows(); ++i) // in database + { + charFound = false; + for (Players::const_iterator it = characters.begin(), + it_end = characters.end(); it != it_end; ++it) // In memory + { + if ( charInMemInfo(i, 0) == (*it)->getName() ) + { + charFound = true; + break; + } + } + if ( !charFound ) + { + // The char is db but not in memory, + // It will be removed from database. + // We store the id of the char to delete + // Because as deleted, the RecordSet is also emptied + // That creates an error. + unsigned int charId = toUint(charInMemInfo(i, 1)); + + // delete the inventory. + std::ostringstream sql5; + sql5 << "delete from "; + sql5 << INVENTORIES_TBL_NAME; + sql5 << " where owner_id = '"; + sql5 << charId; + sql5 << "';"; + mDb->execSql(sql5.str()); + + // now delete the character. + std::ostringstream sql6; + sql6 << "delete from "; + sql6 << CHARACTERS_TBL_NAME; + sql6 << " where id = '"; + sql6 << charId; + sql6 << "';"; + mDb->execSql(sql6.str()); + } + } +} + + +/** + * Delete an account and its associated data from the database. + */ +void DALStorage::delAccount(AccountPtr const &account) +{ + 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()); +} + +/** + * Unload an account from memory. + */ +void DALStorage::unloadAccount(AccountPtr const &account) +{ + flush(account); + mAccounts.erase(account->getID()); +} diff --git a/src/account-server/dalstorage.hpp b/src/account-server/dalstorage.hpp new file mode 100644 index 00000000..22c6c6e8 --- /dev/null +++ b/src/account-server/dalstorage.hpp @@ -0,0 +1,177 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + +#ifndef _TMWSERV_DALSTORAGE_H_ +#define _TMWSERV_DALSTORAGE_H_ + +#include "account-server/storage.hpp" +#include "dal/dataprovider.h" + +/** + * A storage class that relies on DAL. + * + * Notes: + * - this class cannot be instanciated nor duplicated in order to force + * a user class to use the Storage singleton. + */ +class DALStorage: public Storage +{ + // friend so that Storage can call the constructor. + friend class Storage; + + + public: + /** + * Connect to the database and initialize it if necessary. + */ + void + open(void); + + + /** + * Disconnect from the database. + */ + void + close(void); + + + /** + * Get an account by user name. + * + * @param userName the owner of the account. + * + * @return the account associated to the user name. + */ + AccountPtr + getAccount(const std::string& userName); + + + /** + * Add a new account. + * + * @param account the new account. + */ + void + addAccount(const AccountPtr& account); + + + /** + * Delete an account. + * + * @param account the account to delete. + */ + 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. + * @return the list of Email's Addresses. + */ + std::list<std::string> + getEmailList(); + + /** + * Tells if the email address already exists. + * @return true if the email address exists. + */ + bool doesEmailAddressExist(std::string const &email); + + /** + * Tells if the character's name already exists + * @return true if character's name exists. + */ + bool doesCharacterNameExist(std::string const &name); + + /** + * Gives the list of opened public channels registered in database + * @return a map of the public channels + */ + std::map<short, ChatChannel> + getChannelList(); + + /** + * apply channel differences from the list in memory + * to the one in db. + */ + void + updateChannels(std::map<short, ChatChannel>& channelList); + + /** + * Save changes to the database permanently. + * + * @exception tmwserv::dal::DbSqlQueryExecFailure. + */ + void flushAll(); + void flush(AccountPtr const &); + + private: + /** + * Constructor. + */ + DALStorage(void); + + + /** + * Destructor. + */ + ~DALStorage(void) + throw(); + + + /** + * Copy constructor. + */ + DALStorage(const DALStorage& rhs); + + + /** + * Assignment operator. + */ + DALStorage& + operator=(const DALStorage& rhs); + + + /** + * Create the specified table. + * + * @param tblName the table name. + * @param sql the SQL query to execute. + * + * @exception tmwserv::dal::DbSqlQueryExecFailure. + */ + void + createTable(const std::string& tblName, + const std::string& sql); + + + private: + std::auto_ptr<dal::DataProvider> mDb; /**< the data provider */ +}; + +#endif // _TMWSERV_DALSTORAGE_H_ diff --git a/src/account-server/dalstoragesql.hpp b/src/account-server/dalstoragesql.hpp new file mode 100644 index 00000000..391b5572 --- /dev/null +++ b/src/account-server/dalstoragesql.hpp @@ -0,0 +1,334 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + +#ifndef _TMWSERV_DALSTORAGE_SQL_H_ +#define _TMWSERV_DALSTORAGE_SQL_H_ + + +#if !defined (MYSQL_SUPPORT) && !defined (SQLITE_SUPPORT) && \ + !defined (POSTGRESQL_SUPPORT) + +#error "(dalstorage.h) no database backend defined" +#endif + + +#include <string> + +// TODO: Fix problem with PostgreSQL null primary key's. + +/** + * MySQL specificities: + * - TINYINT is an integer (1 byte) type defined as an extension to + * the SQL standard. + * - all integer types can have an optional (non-standard) attribute + * UNSIGNED (http://dev.mysql.com/doc/mysql/en/numeric-types.html) + * + * SQLite3 specificities: + * - any column (but only one for each table) with the exact type of + * 'INTEGER PRIMARY KEY' is taken as auto-increment. + * - the supported data types are: NULL, INTEGER, REAL, TEXT and BLOB + * (http://www.sqlite.org/datatype3.html) + * - the size of TEXT cannot be set, it is just ignored by the engine. + * - IMPORTANT: foreign key constraints are not yet supported + * (http://www.sqlite.org/omitted.html). Included in case of future + * support. + * + * Notes: + * - the SQL queries will take advantage of the most appropriate data + * types supported by a particular database engine in order to + * optimize the server database size. + */ + + +namespace { + + +/** + * TABLE: tmw_accounts. + * + * Notes: + * - the user levels are: + * 0: normal user + * 1: moderator (has medium level rights) + * 2: administrator (i am god :)) + * - the 'banned' field contains the UNIX time of unban (default = 0) + */ +const std::string ACCOUNTS_TBL_NAME("tmw_accounts"); +const std::string SQL_ACCOUNTS_TABLE( + "CREATE TABLE tmw_accounts (" +#if defined (MYSQL_SUPPORT) + "id INTEGER PRIMARY KEY AUTO_INCREMENT," + "username VARCHAR(32) NOT NULL UNIQUE," + "password VARCHAR(32) NOT NULL," + "email VARCHAR(64) NOT NULL," + "level TINYINT UNSIGNED NOT NULL," + "banned TINYINT UNSIGNED NOT NULL," + "INDEX (id)" +#elif defined (SQLITE_SUPPORT) + "id INTEGER PRIMARY KEY," + "username TEXT NOT NULL UNIQUE," + "password TEXT NOT NULL," + "email TEXT NOT NULL," + "level INTEGER NOT NULL," + "banned INTEGER NOT NULL" +#elif defined (POSTGRESQL_SUPPORT) + "id SERIAL PRIMARY KEY," + "username TEXT NOT NULL UNIQUE," + "password TEXT NOT NULL," + "email TEXT NOT NULL," + "level INTEGER NOT NULL," + "banned INTEGER NOT NULL" +#endif + ");" +); + + +/** + * TABLE: tmw_characters. + * + * Notes: + * - the stats will need to be thought over, as we'll be implementing a + * much more elaborate skill based system; we should probably have a + * separate table for storing the skill levels. + * - gender is 0 for male, 1 for female. + */ +const std::string CHARACTERS_TBL_NAME("tmw_characters"); +const std::string SQL_CHARACTERS_TABLE( + "CREATE TABLE tmw_characters (" +#if defined (MYSQL_SUPPORT) + "id INTEGER PRIMARY KEY AUTO_INCREMENT," + "user_id INTEGER UNSIGNED NOT NULL," + "name VARCHAR(32) NOT NULL UNIQUE," + // general information about the character + "gender TINYINT UNSIGNED NOT NULL," + "hair_style TINYINT UNSIGNED NOT NULL," + "hair_color TINYINT UNSIGNED NOT NULL," + "level TINYINT UNSIGNED NOT NULL," + "money INTEGER UNSIGNED NOT NULL," + // location on the map + "x SMALLINT UNSIGNED NOT NULL," + "y SMALLINT UNSIGNED NOT NULL," + "map_id TINYINT NOT NULL," + // stats + "str SMALLINT UNSIGNED NOT NULL," + "agi SMALLINT UNSIGNED NOT NULL," + "vit SMALLINT UNSIGNED NOT NULL," + // note: int must be backquoted as it's a MySQL keyword + "`int` SMALLINT UNSIGNED NOT NULL," + "dex SMALLINT UNSIGNED NOT NULL," + "luck SMALLINT UNSIGNED NOT NULL," + "FOREIGN KEY (user_id) REFERENCES tmw_accounts(id)," + "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)," + "INDEX (id)" +#elif defined (SQLITE_SUPPORT) + "id INTEGER PRIMARY KEY," + "user_id INTEGER NOT NULL," + "name TEXT NOT NULL UNIQUE," + // general information about the character + "gender INTEGER NOT NULL," + "hair_style INTEGER NOT NULL," + "hair_color INTEGER NOT NULL," + "level INTEGER NOT NULL," + "money INTEGER NOT NULL," + // location on the map + "x INTEGER NOT NULL," + "y INTEGER NOT NULL," + "map_id INTEGER NOT NULL," + // stats + "str INTEGER NOT NULL," + "agi INTEGER NOT NULL," + "vit INTEGER NOT NULL," + "int INTEGER NOT NULL," + "dex INTEGER NOT NULL," + "luck INTEGER NOT NULL," + "FOREIGN KEY (user_id) REFERENCES tmw_accounts(id)," + "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)" +#elif defined (POSTGRESQL_SUPPORT) + "id SERIAL PRIMARY KEY," + "user_id INTEGER NOT NULL," + "name TEXT NOT NULL UNIQUE," + // general information about the character + "gender INTEGER NOT NULL," + "hair_style INTEGER NOT NULL," + "hair_color INTEGER NOT NULL," + "level INTEGER NOT NULL," + "money INTEGER NOT NULL," + // location on the map + "x INTEGER NOT NULL," + "y INTEGER NOT NULL," + "map_id INTEGER NOT NULL," + // stats + "str INTEGER NOT NULL," + "agi INTEGER NOT NULL," + "vit INTEGER NOT NULL," + "int INTEGER NOT NULL," + "dex INTEGER NOT NULL," + "luck INTEGER NOT NULL," + "FOREIGN KEY (user_id) REFERENCES tmw_accounts(id)," + "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)" +#endif + ");" +); + + +/** + * TABLE: tmw_items. + * + * Notes: + * - amount: indicates how many items of the same kind can stack. + * - state: (optional) item state saved by script. + */ +const std::string ITEMS_TBL_NAME("tmw_items"); +const std::string SQL_ITEMS_TABLE( + "CREATE TABLE tmw_items (" +#if defined (MYSQL_SUPPORT) + "id SMALLINT PRIMARY KEY AUTO_INCREMENT," + "amount TINYINT UNSIGNED NOT NULL," + "type TINYINT UNSIGNED NOT NULL," + "state TEXT," + "INDEX (id)" +#elif defined (SQLITE_SUPPORT) + "id INTEGER PRIMARY KEY," + "amount INTEGER NOT NULL," + "type INTEGER NOT NULL," + "state TEXT" +#elif defined (POSTGRESQL_SUPPORT) + "id SERIAL PRIMARY KEY," + "amount INTEGER NOT NULL," + "type INTEGER NOT NULL," + "state TEXT" +#endif + ");" +); + + +/** + * TABLE: tmw_world_items. + * + * Notes: + * - store items on the ground in the game world. + */ +const std::string WORLD_ITEMS_TBL_NAME("tmw_world_items"); +// NOTE: Problem here with primary key (only one type of item is allowed on the same map at one time). +const std::string SQL_WORLD_ITEMS_TABLE( + "CREATE TABLE tmw_world_items (" +#if defined (MYSQL_SUPPORT) + "id SMALLINT UNSIGNED NOT NULL," + // location on the map + "x SMALLINT UNSIGNED NOT NULL," + "y SMALLINT UNSIGNED NOT NULL," + "map_id TINYINT NOT NULL," + // time to die (UNIX time) + "deathtime INTEGER UNSIGNED NOT NULL," + "PRIMARY KEY (id, map_id)," + "FOREIGN KEY (id) REFERENCES tmw_items(id)," + "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)" +#elif defined (SQLITE_SUPPORT) + "id INTEGER NOT NULL," + // location on the map + "x INTEGER NOT NULL," + "y INTEGER NOT NULL," + "map_id INTEGER NOT NULL," + // time to die (UNIX time) + "deathtime INTEGER NOT NULL," + "PRIMARY KEY (id, map_id)," + "FOREIGN KEY (id) REFERENCES tmw_items(id)," + "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)" +#elif defined (POSTGRESQL_SUPPORT) + "id INTEGER NOT NULL," + // location on the map + "x INTEGER NOT NULL," + "y INTEGER NOT NULL," + "map_id INTEGER NOT NULL," + // time to die (UNIX time) + "deathtime INTEGER NOT NULL," + "PRIMARY KEY (id, map_id)," + "FOREIGN KEY (id) REFERENCES tmw_items(id)," + "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)" +#endif + ");" +); + + +/** + * TABLE: tmw_inventories. + */ +const std::string INVENTORIES_TBL_NAME("tmw_inventories"); +const std::string SQL_INVENTORIES_TABLE( + "CREATE TABLE tmw_inventories (" +#if defined (MYSQL_SUPPORT) + "id SMALLINT NOT NULL," + "owner_id INTEGER NOT NULL," + "amount SMALLINT NOT NULL," + "equipped TINYINT NOT NULL," + "FOREIGN KEY (id) REFERENCES tmw_items(id)," + "FOREIGN KEY (owner_id) REFERENCES tmw_characters(id)" +#elif defined (SQLITE_SUPPORT) + "id INTEGER NOT NULL," + "owner_id INTEGER NOT NULL," + "amount INTEGER NOT NULL," + "equipped INTEGER NOT NULL," + "FOREIGN KEY (id) REFERENCES tmw_items(id)," + "FOREIGN KEY (owner_id) REFERENCES tmw_characters(id)" +#elif defined (POSTGRESQL_SUPPORT) + "id INTEGER NOT NULL," + "owner_id INTEGER NOT NULL," + "amount INTEGER NOT NULL," + "equipped INTEGER NOT NULL," + "FOREIGN KEY (id) REFERENCES tmw_items(id)," + "FOREIGN KEY (owner_id) REFERENCES tmw_characters(id)" +#endif + ");" +); + +/** + * TABLE: tmw_channels. + * Keeps opened public Channel list + */ +const std::string CHANNELS_TBL_NAME("tmw_channels"); +const std::string SQL_CHANNELS_TABLE( + "CREATE TABLE tmw_channels (" +#if defined (MYSQL_SUPPORT) + "id INTEGER PRIMARY KEY," + "name VARCHAR(32) NOT NULL UNIQUE," + "announcement VARCHAR(256) NOT NULL," + "password VARCHAR(32) NOT NULL" +#elif defined (SQLITE_SUPPORT) + "id INTEGER PRIMARY KEY," + "name TEXT NOT NULL UNIQUE," + "announcement TEXT NOT NULL," + "password TEXT NOT NULL" +#elif defined (POSTGRESQL_SUPPORT) + "id SERIAL PRIMARY KEY," + "name TEXT NOT NULL UNIQUE," + "announcement TEXT NOT NULL," + "password TEXT NOT NULL" +#endif + ");" +); + + +} // anonymous namespace + + +#endif // _TMWSERV_DALSTORAGE_SQL_H_ diff --git a/src/account-server/main-account.cpp b/src/account-server/main-account.cpp new file mode 100644 index 00000000..f462ff08 --- /dev/null +++ b/src/account-server/main-account.cpp @@ -0,0 +1,327 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include <cstdlib> +#include <getopt.h> +#include <signal.h> +#include <iostream> +#include <physfs.h> +#include <enet/enet.h> + +#if (defined __USE_UNIX98 || defined __FreeBSD__) +#include "../config.h" +#elif defined WIN32 +#include "../tmwserv_private.h" +#define PACKAGE_VERSION PRODUCT_VERSION +#endif + +#include "configuration.h" +#include "resourcemanager.h" +#include "skill.h" +#include "account-server/accounthandler.hpp" +#include "account-server/serverhandler.hpp" +#include "account-server/storage.hpp" +#include "chat-server/chatchannelmanager.hpp" +#include "chat-server/chathandler.hpp" +#include "net/connectionhandler.hpp" +#include "net/messageout.hpp" +#include "utils/logger.h" +#include "utils/stringfilter.h" +#include "utils/timer.h" + +// Default options that automake should be able to override. +#define DEFAULT_LOG_FILE "tmwserv.log" +#define DEFAULT_CONFIG_FILE "tmwserv.xml" +#define DEFAULT_ITEMSDB_FILE "items.xml" + +utils::Timer worldTimer(100, false); /**< Timer for world tics set to 100 ms */ +int worldTime = 0; /**< Current world time in 100ms ticks */ +bool running = true; /**< Determines if server keeps running */ + +Skill skillTree("base"); /**< Skill tree */ + +Configuration config; /**< XML config reader */ + +utils::StringFilter *stringFilter; /**< Slang's Filter */ + +/** Account message handler */ +AccountHandler *accountHandler; + +/** Communications (chat) message handler */ +ChatHandler *chatHandler; + +/** Server message handler */ +ServerHandler *serverHandler; + +/** Chat Channels Manager */ +ChatChannelManager *chatChannelManager; + +/** + * Initializes the server. + */ +void initialize() +{ + + // Reset to default segmentation fault handling for debugging purposes + signal(SIGSEGV, SIG_DFL); + + // Set enet to quit on exit. + atexit(enet_deinitialize); + + /* + * If the path values aren't defined, we set the default + * depending on the platform. + */ + // The config path +#if defined CONFIG_FILE + std::string configPath = CONFIG_FILE; +#else + +#if (defined __USE_UNIX98 || defined __FreeBSD__) + std::string configPath = getenv("HOME"); + configPath += "/."; + configPath += DEFAULT_CONFIG_FILE; +#else // Win32, ... + std::string configPath = DEFAULT_CONFIG_FILE; +#endif + +#endif // defined CONFIG_FILE + + // The log path +#if defined LOG_FILE + std::string logPath = LOG_FILE; +#else + +#if (defined __USE_UNIX98 || defined __FreeBSD__) + std::string logPath = getenv("HOME"); + logPath += "/."; + logPath += DEFAULT_LOG_FILE; +#else // Win32, ... + std::string logPath = DEFAULT_LOG_FILE; +#endif + +#endif // defined LOG_FILE + + // Initialize PhysicsFS + PHYSFS_init(""); + + // Initialize the logger. + using namespace utils; + Logger::instance().setLogFile(logPath); + + // write the messages to both the screen and the log file. + Logger::instance().setTeeMode(true); + + config.init(configPath); + LOG_INFO("Using Config File: " << configPath, 0); + LOG_INFO("Using Log File: " << logPath, 0); + + // --- Initialize the managers + // Initialize the slang's and double quotes filter. + stringFilter = new StringFilter(&config); + // Initialize the Chat channels manager + chatChannelManager = new ChatChannelManager(); + + // --- Initialize the global handlers + // FIXME: Make the global handlers global vars or part of a bigger + // singleton or a local variable in the event-loop + accountHandler = new AccountHandler(); + chatHandler = new ChatHandler(); + serverHandler = new ServerHandler(); + + // --- Initialize enet. + if (enet_initialize() != 0) { + LOG_FATAL("An error occurred while initializing ENet", 0); + exit(2); + } + + +#if defined (MYSQL_SUPPORT) + LOG_INFO("Using MySQL DB Backend.", 0); +#elif defined (POSTGRESQL_SUPPORT) + LOG_INFO("Using PostGreSQL DB Backend.", 0); +#elif defined (SQLITE_SUPPORT) + LOG_INFO("Using SQLite DB Backend.", 0); +#else + LOG_WARN("No Database Backend Support.", 0); +#endif + + // Initialize configuration defaults + config.setValue("dbuser", ""); + config.setValue("dbpass", ""); + config.setValue("dbhost", ""); +} + + +/** + * Deinitializes the server. + */ +void deinitialize() +{ + delete stringFilter; + // Write configuration file + config.write(); + + // Stop world timer + worldTimer.stop(); + + // Quit ENet + enet_deinitialize(); + + // Destroy message handlers + delete serverHandler; + delete chatHandler; + delete accountHandler; + + // Destroy Managers + delete chatChannelManager; + + // Get rid of persistent data storage + Storage::destroy(); + + PHYSFS_deinit(); +} + + +/** + * Show command line arguments + */ +void printHelp() +{ + std::cout << "tmwserv" << std::endl << std::endl + << "Options: " << std::endl + << " -h --help : Display this help" << std::endl + << " --verbosity <n> : Set the verbosity level" << std::endl + << " --port <n> : Set the default port to listen on" << std::endl; + exit(0); +} + +/** + * Parse the command line arguments + */ +void parseOptions(int argc, char *argv[]) +{ + const char *optstring = "h"; + + const struct option long_options[] = { + { "help", no_argument, 0, 'h' }, + { "verbosity", required_argument, 0, 'v' }, + { "port", required_argument, 0, 'p' }, + { 0 } + }; + + while (optind < argc) { + int result = getopt_long(argc, argv, optstring, long_options, NULL); + + if (result == -1) { + break; + } + + switch (result) { + default: // Unknown option + case 'h': + // Print help + printHelp(); + break; + case 'v': + // Set Verbosity to level + unsigned short verbosityLevel; + verbosityLevel = atoi(optarg); + utils::Logger::instance().setVerbosity(verbosityLevel); + LOG_INFO("Setting Log Verbosity Level to " << verbosityLevel, 0); + break; + case 'p': + // Change the port to listen on. + unsigned short portToListenOn; + portToListenOn = atoi(optarg); + config.setValue("ListenOnPort", portToListenOn); + LOG_INFO("Setting Default Port to " << portToListenOn, 0); + break; + } + } +} + + +/** + * Main function, initializes and runs server. + */ +int main(int argc, char *argv[]) +{ + int elapsedWorldTicks; + + LOG_INFO("The Mana World Server v" << PACKAGE_VERSION, 0); + + // Parse Command Line Options + parseOptions(argc, argv); + + // General Initialization + initialize(); + + int port = int(config.getValue("accountServerPort", DEFAULT_SERVER_PORT)); + if (!accountHandler->startListen(port) || + !serverHandler->startListen(port + 1) || + !chatHandler->startListen(port + 2)) { + LOG_ERROR("Unable to create an ENet server host.", 0); + return 3; + } + + // Create storage wrapper + Storage& store = Storage::instance("tmw"); + store.setUser(config.getValue("dbuser", "")); + store.setPassword(config.getValue("dbpass", "")); + store.close(); + store.open(); + + // Initialize world timer + worldTimer.start(); + + while (running) { + elapsedWorldTicks = worldTimer.poll(); + if (elapsedWorldTicks > 0) { + worldTime += elapsedWorldTicks; + + if (elapsedWorldTicks > 1) + { + LOG_WARN(elapsedWorldTicks -1 << " World Tick(s) skipped " + "because of insufficient time. please buy a faster " + "machine ;-)", 0); + }; + + // Print world time at 10 second intervals to show we're alive + if (worldTime % 100 == 0) { + LOG_INFO("World time: " << worldTime, 0); + } + + // Handle all messages that are in the message queues + accountHandler->process(); + chatHandler->process(); + serverHandler->process(); + } + worldTimer.sleep(); + } + + LOG_INFO("Received: Quit signal, closing down...", 0); + serverHandler->stopListen(); + chatHandler->stopListen(); + accountHandler->stopListen(); + deinitialize(); +} diff --git a/src/account-server/serverhandler.cpp b/src/account-server/serverhandler.cpp new file mode 100644 index 00000000..5300a066 --- /dev/null +++ b/src/account-server/serverhandler.cpp @@ -0,0 +1,115 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include <cassert> +#include <sstream> + +#include "account-server/serverhandler.hpp" +#include "net/messagein.hpp" +#include "net/messageout.hpp" +#include "net/netcomputer.hpp" +#include "utils/logger.h" + +bool ServerHandler::startListen(enet_uint16 port) +{ + LOG_INFO("Server handler started:", 0); + return ConnectionHandler::startListen(port); +} + +NetComputer *ServerHandler::computerConnected(ENetPeer *peer) +{ + return new NetComputer(peer); +} + +void ServerHandler::computerDisconnected(NetComputer *comp) +{ + delete comp; +} + +bool ServerHandler::getGameServerFromMap(unsigned mapId, std::string &address, short &port) +{ + Servers::const_iterator i = servers.find(mapId); + if (i == servers.end()) return false; + address = i->second.address; + port = i->second.port; + return true; +} + +void ServerHandler::registerGameClient(std::string const &token, PlayerPtr ptr) +{ + unsigned mapId = ptr->getMapId(); + MessageOut msg(AGMSG_PLAYER_ENTER); + msg.writeLong(ptr->getDatabaseID()); + msg.writeString(ptr->getName()); + msg.writeByte(ptr->getGender()); + msg.writeByte(ptr->getHairStyle()); + msg.writeByte(ptr->getHairColor()); + msg.writeByte(ptr->getLevel()); + msg.writeShort(ptr->getMoney()); + for (int j = 0; j < NB_RSTAT; ++j) + msg.writeShort(ptr->getRawStat(j)); + Point pos = ptr->getPosition(); + msg.writeShort(pos.x); + msg.writeShort(pos.y); + msg.writeShort(mapId); + msg.writeString(token, 32); + Servers::const_iterator i = servers.find(mapId); + assert(i != servers.end()); + i->second.server->send(msg); +} + +void ServerHandler::processMessage(NetComputer *comp, MessageIn &msg) +{ + MessageOut result; + + switch (msg.getId()) + { + case GAMSG_REGISTER: + { + // TODO: check the credentials of the game server + std::string address = msg.readString(); + int port = msg.readShort(); + Server s = { address, port, comp }; + LOG_INFO("Game server " << address << ':' << port + << " wants to register " << (msg.getUnreadLength() / 2) + << " maps.", 0); + while (msg.getUnreadLength()) + { + unsigned id = msg.readShort(); + if (!servers.insert(std::make_pair(id, s)).second) + { + LOG_ERROR("Server Handler: map is already registered.", 0); + } + } + } break; + + default: + LOG_WARN("Invalid message type.", 0); + result.writeShort(XXMSG_INVALID); + break; + } + + // return result + if (result.getLength() > 0) + comp->send(result); +} diff --git a/src/account-server/serverhandler.hpp b/src/account-server/serverhandler.hpp new file mode 100644 index 00000000..797e0a48 --- /dev/null +++ b/src/account-server/serverhandler.hpp @@ -0,0 +1,90 @@ +/* + * The Mana World Server + * Copyright 2006 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 + * any later version. + * + * The Mana World is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#ifndef _TMWSERV_SERVERHANDLER_H_ +#define _TMWSERV_SERVERHANDLER_H_ + +#include <map> + +#include "player.h" +#include "net/connectionhandler.hpp" + +/** + * Manages communications with all the game servers. This class also keeps + * track of the maps each game server supports. + */ +class ServerHandler: public ConnectionHandler +{ + public: + /** + * Starts the handler on the given port. + */ + bool startListen(enet_uint16 port); + + /** + * Returns the information a client needs to connect to the game server + * corresponding to the given map ID. + */ + bool getGameServerFromMap(unsigned, std::string &address, short &port); + + /** + * Sends a magic token and player data to the relevant game server. + */ + void registerGameClient(std::string const &, PlayerPtr); + + protected: + /** + * Processes server messages. + */ + void processMessage(NetComputer *computer, MessageIn &message); + + /** + * Called when a game server connects. Initializes a simple NetComputer + * as these connections are stateless. + */ + NetComputer *computerConnected(ENetPeer *peer); + + /** + * Called when a game server disconnects. + */ + void computerDisconnected(NetComputer *comp); + + private: + struct Server + { + std::string address; + short port; + NetComputer *server; + }; + + typedef std::map< unsigned, Server > Servers; + + /** + * Maps map IDs to game server data. + */ + Servers servers; +}; + +extern ServerHandler *serverHandler; + +#endif diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp new file mode 100644 index 00000000..a02ec010 --- /dev/null +++ b/src/account-server/storage.cpp @@ -0,0 +1,145 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include "account-server/dalstorage.hpp" +#include "account-server/storage.hpp" + +// initialize the static attributes. +Storage* Storage::mInstance = 0; +std::string Storage::mName(""); +std::string Storage::mUser(""); +std::string Storage::mPassword(""); + + +/** + * Constructor. + */ +Storage::Storage(void) + throw() +{ + // NOOP +} + + +/** + * Destructor. + */ +Storage::~Storage(void) + throw() +{ + // NOOP +} + + +/** + * Create an instance of Storage. + */ +Storage& +Storage::instance(const std::string& name) +{ + if (mInstance == 0) { + mInstance = new DALStorage(); + + // set the name of the storage. + mName = name; + } + + return (*mInstance); +} + + +/** + * Delete the instance. + */ +void +Storage::destroy(void) +{ + if (mInstance != 0) { + delete mInstance; + mInstance = 0; + } + + // reset the attributes. + mName = ""; + mUser = ""; + mPassword = ""; +} + + +/** + * Check if the storage is open. + */ +bool +Storage::isOpen(void) const +{ + return mIsOpen; +} + + +/** + * Get the storage name. + */ +const std::string& +Storage::getName(void) const +{ + return mName; +} + + +/** + * Set a user name for the storage. + */ +void +Storage::setUser(const std::string& userName) +{ + mUser = userName; +} + + +/** + * Get the user name. + */ +const std::string& +Storage::getUser(void) const +{ + return mUser; +} + + +/** + * Set a user password for the storage. + */ +void +Storage::setPassword(const std::string& password) +{ + mPassword = password; +} + + +/** + * Get the user password. + */ +const std::string& +Storage::getPassword(void) const +{ + return mPassword; +} diff --git a/src/account-server/storage.hpp b/src/account-server/storage.hpp new file mode 100644 index 00000000..fec6dcf3 --- /dev/null +++ b/src/account-server/storage.hpp @@ -0,0 +1,290 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + +#ifndef _TMWSERV_STORAGE_H_ +#define _TMWSERV_STORAGE_H_ + +#include <list> +#include <map> + +#include "account-server/account.hpp" +#include "chat-server/chatchannel.hpp" + +/** + * Data type for the list of accounts. + */ +typedef std::map<unsigned, AccountPtr> Accounts; + + +/** + * A storage to load and persist dynamic data. + * + * Notes: + * - this class implements the singleton design pattern. + * - destroy() must be called at least once before the application + * exits or else there will be a memory leak. + */ +class Storage +{ + public: + /** + * Create a named instance of Storage. + * + * @param name the name of the storage. + * + * @return the unique instance of Storage. + * + * @exception std::bad_alloc if the instance cannot be created. + * + * Notes: + * - it is up to the underlying implementation of Storage to + * decide about what to do with the name, it could serve as the + * name of the database or the name of the file into which the + * storage will be dumped to. + * - the name of the storage is saved only when it's the first + * invocation of instance() or only when instance() is invoked + * after destroy(). + */ + static Storage& + instance(const std::string& name); + + + /** + * Delete the storage. + */ + static void + destroy(void); + + + /** + * Open the storage for read/write access. + * + * Depending on the underlying implementation of Storage, opening + * a storage would mean either opening a file or connecting to a + * database. + */ + virtual void + open(void) = 0; + + + /** + * Close the storage. + * + * Depending on the underlying implementation of Storage, closing + * a storage would mean either closing a file or disconnecting from + * a database. + */ + virtual void + close(void) = 0; + + + /** + * Check if the storage is open. + * + * @return true if the storage is open. + */ + bool + isOpen(void) const; + + + /** + * Get the storage name. + * + * @return the storage name. + */ + const std::string& + getName(void) const; + + + /** + * Set a user name for the storage. + * + * Depending on the underlying implementation of Storage, setting + * the user name may have no effect (e.g. DALStorage running on + * SQLite). + * + * @param userName the user name. + */ + void + setUser(const std::string& userName); + + + /** + * Get the user name. + * + * @return the user name (it may be an empty string if not set + * previously). + */ + const std::string& + getUser(void) const; + + + /** + * Set a user password for the storage. + * + * Depending on the underlying implementation of Storage, setting + * the user password may have no effect (e.g. DALStorage running on + * SQLite). + * + * @param password the user password. + */ + void + setPassword(const std::string& password); + + + /** + * Get the user password. + * + * @return the user password (it may be an empty string if not set + * previously). + */ + const std::string& + getPassword(void) const; + + + /** + * Get an account by user name. + * + * @param userName the owner of the account. + * + * @return the account associated to the user name. + */ + virtual AccountPtr + getAccount(const std::string& userName) = 0; + + /** + * Add a new account. + * + * @param account the new account. + */ + virtual void + addAccount(const AccountPtr& account) = 0; + + /** + * Delete an account. + * + * @param account the account to delete. + */ + virtual void + 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. + * @return the list of Email's Addresses. + * + * @deprecated The only purpose of using this list inside the server is + * for checking for existing email addresses, which is + * covered by Storage::getSameEmailNumber(). + * It could later be used for mailing list announcement. + */ + virtual + std::list<std::string> getEmailList() = 0; + + /** + * Tells if the email address already exists. + * @return true if the email address exists. + */ + virtual bool doesEmailAddressExist(std::string const &email) = 0; + + /** + * Tells if the character's name already exists + * @return true if character's name exists. + */ + virtual bool doesCharacterNameExist(std::string const &name) = 0; + + /** + * Gives the list of opened public channels registered in database + * @return a map of the public channels + */ + virtual std::map<short, ChatChannel> + getChannelList() = 0; + + /** + * apply channel differences from the list in memory + * to the one in db. + */ + virtual void + updateChannels(std::map<short, ChatChannel>& channelList) = 0; + + /** + * Saves the changes to all the accounts permanently. + */ + virtual void flushAll() = 0; + + /** + * Saves the changes to one account permanently. + */ + virtual void flush(AccountPtr const &account) = 0; + + + protected: + /** + * Default constructor. + */ + Storage(void) + throw(); + + + /** + * Destructor. + */ + virtual + ~Storage(void) + throw(); + + + /** + * Copy constructor. + */ + Storage(const Storage& rhs); + + + /** + * Assignment operator. + */ + Storage& + operator=(const Storage& rhs); + + + protected: + Accounts mAccounts; /**< list of accounts in memory */ + Players mCharacters; /**< the loaded characters */ + bool mIsOpen; /**< flag is true if the storage is open */ + + + private: + static Storage* mInstance; /**< the unique instance of Storage */ + static std::string mName; /**< the name of the storage */ + static std::string mUser; /**< the user name */ + static std::string mPassword; /**< the user password */ +}; + +#endif // _TMWSERV_STORAGE_H_ |