#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Eris/Account.h>
#include <Eris/Connection.h>
#include <Eris/LogStream.h>
#include <Eris/Exceptions.h>
#include <Eris/Avatar.h>
#include <Eris/Router.h>
#include <Eris/Response.h>
#include <Eris/DeleteLater.h>
#include <Atlas/Objects/Entity.h>
#include <Atlas/Objects/Operation.h>
#include <Atlas/Objects/Anonymous.h>
#include <algorithm>
#include <cassert>
using Atlas::Objects::Root;
using namespace Atlas::Objects::Operation;
using Atlas::Objects::Entity::RootEntity;
using Atlas::Objects::Entity::Anonymous;
typedef Atlas::Objects::Entity::Account AtlasAccount;
using Atlas::Objects::smart_dynamic_cast;
namespace Eris {
class AccountRouter : public Router
{
public:
AccountRouter(Account* pl) :
m_account(pl)
{
m_account->getConnection()->setDefaultRouter(this);
}
virtual ~AccountRouter()
{
m_account->getConnection()->clearDefaultRouter();
}
virtual RouterResult handleOperation(const RootOperation& op)
{
// logout
if (op->getClassNo() == LOGOUT_NO) {
debug() << "Account reciev forced logout from server";
m_account->internalLogout(false);
return HANDLED;
}
if ((op->getClassNo() == SIGHT_NO) && (op->getTo() == m_account->getId()))
{
const std::vector<Root>& args = op->getArgs();
AtlasAccount acc = smart_dynamic_cast<AtlasAccount>(args.front());
m_account->updateFromObject(acc);
// refresh character data if it changed
if (!acc->isDefaultCharacters()) m_account->refreshCharacterInfo();
return HANDLED;
}
return IGNORED;
}
private:
Account* m_account;
};
#pragma mark -
Account::Account(Connection *con) :
m_con(con),
m_status(DISCONNECTED),
m_doingCharacterRefresh(false)
{
if (!m_con) throw InvalidOperation("invalid Connection passed to Account");
m_router = new AccountRouter(this);
m_con->Connected.connect(sigc::mem_fun(this, &Account::netConnected));
m_con->Failure.connect(sigc::mem_fun(this, &Account::netFailure));
}
Account::~Account()
{
ActiveCharacterMap::iterator it;
for (it = m_activeCharacters.begin(); it != m_activeCharacters.end(); )
{
ActiveCharacterMap::iterator cur = it++;
deactivateCharacter(cur->second); // send logout op
// cur gets invalidated by innerDeactivateCharacter
delete cur->second;
}
if (isLoggedIn()) logout();
delete m_router;
}
Result Account::login(const std::string &uname, const std::string &password)
{
if (!m_con->isConnected()) {
error() << "called login on unconnected Connection";
return NOT_CONNECTED;
}
if (m_status != DISCONNECTED) {
error() << "called login, but state is not currently disconnected";
return ALREADY_LOGGED_IN;
}
return internalLogin(uname, password);
}
Result Account::createAccount(const std::string &uname,
const std::string &fullName,
const std::string &pwd)
{
if (!m_con->isConnected()) return NOT_CONNECTED;
if (m_status != DISCONNECTED) return ALREADY_LOGGED_IN;
m_status = LOGGING_IN;
// okay, build and send the create(account) op
AtlasAccount account;
account->setPassword(pwd);
account->setName(fullName);
account->setUsername(uname);
Create c;
c->setSerialno(getNewSerialno());
c->setArgs1(account);
m_con->getResponder()->await(c->getSerialno(), this, &Account::loginResponse);
m_con->send(c);
// store for re-logins
m_username = uname;
m_pass = pwd;
m_timeout.reset(new Timeout(5000));
m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLoginTimeout));
return NO_ERR;
}
Result Account::logout()
{
if (!m_con->isConnected()) {
error() << "called logout on bad connection ignoring";
return NOT_CONNECTED;
}
if (m_status == LOGGING_OUT) return NO_ERR;
if (m_status != LOGGED_IN) {
error() << "called logout on non-logged-in Account";
return NOT_LOGGED_IN;
}
m_status = LOGGING_OUT;
Logout l;
Anonymous arg;
arg->setId(m_accountId);
l->setArgs1(arg);
l->setSerialno(getNewSerialno());
m_con->getResponder()->await(l->getSerialno(), this, &Account::logoutResponse);
m_con->send(l);
m_timeout.reset(new Timeout(5000));
m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLogoutTimeout));
return NO_ERR;
}
const std::vector< std::string > & Account::getCharacterTypes(void) const
{
return m_characterTypes;
}
const CharacterMap& Account::getCharacters()
{
if (m_status != LOGGED_IN)
error() << "Not logged into an account : getCharacter returning empty dictionary";
return _characters;
}
Result Account::refreshCharacterInfo()
{
if (!m_con->isConnected()) return NOT_CONNECTED;
if (m_status != LOGGED_IN) return NOT_LOGGED_IN;
// silently ignore overlapping refreshes
if (m_doingCharacterRefresh) return NO_ERR;
_characters.clear();
if (m_characterIds.empty())
{
GotAllCharacters.emit(); // we must emit the done signal
return NO_ERR;
}
// okay, now we know we have at least one character to lookup, set the flag
m_doingCharacterRefresh = true;
Look lk;
Anonymous obj;
lk->setFrom(m_accountId);
for (StringSet::iterator I=m_characterIds.begin(); I!=m_characterIds.end(); ++I)
{
obj->setId(*I);
lk->setArgs1(obj);
lk->setSerialno(getNewSerialno());
m_con->getResponder()->await(lk->getSerialno(), this, &Account::sightCharacter);
m_con->send(lk);
}
return NO_ERR;
}
Result Account::createCharacter(const Atlas::Objects::Entity::RootEntity &ent)
{
if (!m_con->isConnected()) return NOT_CONNECTED;
if (m_status != LOGGED_IN) {
if ((m_status == CREATING_CHAR) || (m_status == TAKING_CHAR)) {
error() << "duplicate char creation / take";
return DUPLICATE_CHAR_ACTIVE;
} else {
error() << "called createCharacter on unconnected Account, ignoring";
return NOT_LOGGED_IN;
}
}
Create c;
c->setArgs1(ent);
c->setFrom(m_accountId);
c->setSerialno(getNewSerialno());
m_con->send(c);
m_con->getResponder()->await(c->getSerialno(), this, &Account::avatarResponse);
m_status = CREATING_CHAR;
return NO_ERR;
}
/*
void Account::createCharacter()
{
if (!_lobby || _lobby->getAccountID().empty())
throw InvalidOperation("no account exists!");
if (!_con->isConnected())
throw InvalidOperation("Not connected to server");
throw InvalidOperation("No UserInterface handler defined");
// FIXME look up the dialog, create the instance,
// hook in a slot to feed the serialno of any Create op
// the dialog passes back to createCharacterHandler()
}
void Account::createCharacterHandler(long serialno)
{
if (serialno)
NewCharacter((new World(this, _con))->createAvatar(serialno));
}
*/
Result Account::takeCharacter(const std::string &id)
{
if (m_characterIds.count(id) == 0) {
error() << "Character '" << id << "' not owned by Account " << m_username;
return BAD_CHARACTER_ID;
}
if (!m_con->isConnected()) return NOT_CONNECTED;
if (m_status != LOGGED_IN) {
if ((m_status == CREATING_CHAR) || (m_status == TAKING_CHAR)) {
error() << "duplicate char creation / take";
return DUPLICATE_CHAR_ACTIVE;
} else {
error() << "called createCharacter on unconnected Account, ignoring";
return NOT_LOGGED_IN;
}
}
Anonymous what;
what->setId(id);
Look l;
l->setFrom(id); // should this be m_accountId?
l->setArgs1(what);
l->setSerialno(getNewSerialno());
m_con->send(l);
m_con->getResponder()->await(l->getSerialno(), this, &Account::avatarResponse);
m_status = TAKING_CHAR;
return NO_ERR;
}
Result Account::deactivateCharacter(Avatar* av)
{
av->deactivate();
return NO_ERR;
}
bool Account::isLoggedIn() const
{
return ((m_status == LOGGED_IN) ||
(m_status == TAKING_CHAR) || (m_status == CREATING_CHAR));
}
#pragma mark -
Result Account::internalLogin(const std::string &uname, const std::string &pwd)
{
assert(m_status == DISCONNECTED);
m_status = LOGGING_IN;
m_username = uname; // store for posterity
AtlasAccount account;
account->setPassword(pwd);
account->setUsername(uname);
Login l;
l->setArgs1(account);
l->setSerialno(getNewSerialno());
m_con->getResponder()->await(l->getSerialno(), this, &Account::loginResponse);
m_con->send(l);
m_timeout.reset(new Timeout(5000));
m_timeout->Expired.connect(sigc::mem_fun(this, &Account::handleLoginTimeout));
return NO_ERR;
}
void Account::logoutResponse(const RootOperation& op)
{
if (!op->instanceOf(INFO_NO))
warning() << "received a logout response that is not an INFO";
internalLogout(true);
}
void Account::internalLogout(bool clean)
{
if (clean) {
if (m_status != LOGGING_OUT)
error() << "got clean logout, but not logging out already";
} else {
if ((m_status != LOGGED_IN) && (m_status != TAKING_CHAR) && (m_status != CREATING_CHAR))
error() << "got forced logout, but not currently logged in";
}
m_con->unregisterRouterForTo(m_router, m_accountId);
m_status = DISCONNECTED;
m_timeout.reset();
if (m_con->getStatus() == BaseConnection::DISCONNECTING) {
m_con->unlock();
} else {
LogoutComplete.emit(clean);
}
}
void Account::loginResponse(const RootOperation& op)
{
if (op->instanceOf(ERROR_NO)) {
loginError(smart_dynamic_cast<Error>(op));
} else if (op->instanceOf(INFO_NO)) {
const std::vector<Root>& args = op->getArgs();
loginComplete(smart_dynamic_cast<AtlasAccount>(args.front()));
} else
warning() << "received malformed login response: " << op->getClassNo();
}
void Account::loginComplete(const AtlasAccount &p)
{
if (m_status != LOGGING_IN)
error() << "got loginComplete, but not currently logging in!";
if (p->getUsername() != m_username)
error() << "missing or incorrect username on login INFO";
m_status = LOGGED_IN;
m_accountId = p->getId();
m_con->registerRouterForTo(m_router, m_accountId);
updateFromObject(p);
// notify an people watching us
LoginSuccess.emit();
m_con->Disconnecting.connect(sigc::mem_fun(this, &Account::netDisconnecting));
m_timeout.reset();
}
void Account::updateFromObject(const AtlasAccount &p)
{
m_characterIds = StringSet(p->getCharacters().begin(), p->getCharacters().end());
if(p->hasAttr("character_types") == true)
{
Atlas::Message::Element CharacterTypes(p->getAttr("character_types"));
if(CharacterTypes.isList() == true)
{
const Atlas::Message::ListType & CharacterTypesList(CharacterTypes.asList());
Atlas::Message::ListType::const_iterator iCharacterType(CharacterTypesList.begin());
Atlas::Message::ListType::const_iterator iEnd(CharacterTypesList.end());
m_characterTypes.reserve(CharacterTypesList.size());
while(iCharacterType != iEnd)
{
if(iCharacterType->isString() == true)
{
m_characterTypes.push_back(iCharacterType->asString());
}
else
{
error() << "An element of the \"character_types\" list is not a String.";
}
++iCharacterType;
}
}
else
{
error() << "Account has attribute \"character_types\" which is not of type List.";
}
}
}
void Account::loginError(const Error& err)
{
assert(err.isValid());
if (m_status != LOGGING_IN) {
error() << "got loginError while not logging in";
}
const std::vector<Root>& args = err->getArgs();
std::string msg = args[0]->getAttr("message").asString();
// update state before emitting signal
m_status = DISCONNECTED;
m_timeout.reset();
LoginFailure.emit(msg);
}
void Account::handleLoginTimeout()
{
m_status = DISCONNECTED;
deleteLater(m_timeout.release());
LoginFailure.emit("timed out waiting for server response");
}
void Account::avatarResponse(const RootOperation& op)
{
if (op->instanceOf(ERROR_NO)) {
const std::vector<Root>& args = op->getArgs();
std::string msg = args[0]->getAttr("message").asString();
// creating or taking a character failed for some reason
AvatarFailure(msg);
m_status = Account::LOGGED_IN;
} else if (op->instanceOf(INFO_NO)) {
const std::vector<Root>& args = op->getArgs();
RootEntity ent = smart_dynamic_cast<RootEntity>(args.front());
if (!ent.isValid()) {
warning() << "malformed character create/take response";
return;
}
Avatar* av = new Avatar(this, ent->getId());
AvatarSuccess.emit(av);
m_status = Account::LOGGED_IN;
assert(m_activeCharacters.count(av->getId()) == 0);
m_activeCharacters[av->getId()] = av;
// expect another op with the same refno
m_con->getResponder()->ignore(op->getRefno());
} else
warning() << "received malformed avatar take response";
}
void Account::internalDeactivateCharacter(Avatar* av)
{
assert(m_activeCharacters.count(av->getId()) == 1);
m_activeCharacters.erase(av->getId());
}
void Account::sightCharacter(const RootOperation& op)
{
if (!m_doingCharacterRefresh) {
error() << "got sight of character outside a refresh, ignoring";
return;
}
const std::vector<Root>& args = op->getArgs();
assert(!args.empty());
RootEntity ge = smart_dynamic_cast<RootEntity>(args.front());
assert(ge.isValid());
CharacterMap::iterator C = _characters.find(ge->getId());
if (C != _characters.end()) {
error() << "duplicate sight of character " << ge->getId();
return;
}
// okay, we can now add it to our map
_characters.insert(C, CharacterMap::value_type(ge->getId(), ge));
GotCharacterInfo.emit(ge);
// check if we're done
if (_characters.size() == m_characterIds.size()) {
m_doingCharacterRefresh = false;
GotAllCharacters.emit();
}
}
/* this will only ever get encountered after the connection is initally up;
thus we use it to trigger a reconnection. Note that some actions by
Lobby and World are also required to get everything back into the correct
state */
void Account::netConnected()
{
// re-connection
if (!m_username.empty() && !m_pass.empty() && (m_status == DISCONNECTED)) {
debug() << "Account " << m_username << " got netConnected, doing reconnect";
internalLogin(m_username, m_pass);
}
}
bool Account::netDisconnecting()
{
if (m_status == LOGGED_IN) {
m_con->lock();
logout();
return false;
} else
return true;
}
void Account::netFailure(const std::string& /*msg*/)
{
}
void Account::handleLogoutTimeout()
{
error() << "LOGOUT timed out waiting for response";
m_status = DISCONNECTED;
deleteLater(m_timeout.release());
LogoutComplete.emit(false);
}
void Account::avatarLogoutResponse(const RootOperation& op)
{
if (!op->instanceOf(INFO_NO))
warning() << "received an avatar logout response that is not an INFO";
const std::vector<Root>& args(op->getArgs());
if (args.empty() || (args.front()->getClassNo() != LOGOUT_NO)) {
warning() << "argument of avatar logout INFO is not a logout op";
return;
}
RootOperation logout = smart_dynamic_cast<RootOperation>(args.front());
const std::vector<Root>& args2(logout->getArgs());
assert(!args2.empty());
std::string charId = args2.front()->getId();
debug() << "got logout for character " << charId;
if (!m_characterIds.count(charId)) {
warning() << "character ID " << charId << " is unknown on account " << m_accountId;
}
ActiveCharacterMap::iterator it = m_activeCharacters.find(charId);
if (it == m_activeCharacters.end()) {
warning() << "character ID " << charId << " does not crrespond to an active avatar.";
return;
}
AvatarDeactivated.emit(it->second);
delete it->second; // will call back into internalDeactivateCharacter
}
} // of namespace Eris
syntax highlighted by Code2HTML, v. 0.9.1