diff --git a/astron/databases/.gitignore b/astron/databases/.gitignore new file mode 100644 index 0000000..98e6ef6 --- /dev/null +++ b/astron/databases/.gitignore @@ -0,0 +1 @@ +*.db diff --git a/astron/databases/astrondb/.gitignore b/astron/databases/astrondb/.gitignore new file mode 100644 index 0000000..1e82fc7 --- /dev/null +++ b/astron/databases/astrondb/.gitignore @@ -0,0 +1 @@ +*.yaml diff --git a/etc/otp.dc b/etc/otp.dc index b3438dc..937921a 100755 --- a/etc/otp.dc +++ b/etc/otp.dc @@ -4,6 +4,7 @@ from direct.distributed import DistributedSmoothNode/AI from direct.distributed import DistributedCartesianGrid/AI from direct.distributed import DistributedCamera/AI/OV from otp.distributed import Account/AI/UD +from otp.distributed import AstronAccount/AI/UD from otp.ai import TimeManager/AI from otp.ai import MagicWordManager/AI from otp.avatar import DistributedAvatar/AI/UD @@ -53,6 +54,15 @@ dclass Account { string LAST_LOGIN db; }; +dclass AstronAccount { + uint32[] ACCOUNT_AV_SET required db; + uint32 ESTATE_ID db; + AvatarPendingDel ACCOUNT_AV_SET_DEL[] db; + string CREATED db; + string LAST_LOGIN db; + string ACCOUNT_ID db; +}; + struct BarrierData { uint16 context; string name; @@ -515,4 +525,3 @@ dclass AstronLoginManager : DistributedObject { requestLogin(string) clsend; loginResponse(blob); }; - diff --git a/otp/distributed/AstronAccount.py b/otp/distributed/AstronAccount.py new file mode 100644 index 0000000..d984fec --- /dev/null +++ b/otp/distributed/AstronAccount.py @@ -0,0 +1,6 @@ +from direct.distributed import DistributedObject + +class AstronAccount(DistributedObject.DistributedObject): + + def __init__(self, cr): + pass diff --git a/otp/distributed/AstronAccountAI.py b/otp/distributed/AstronAccountAI.py new file mode 100644 index 0000000..e5dc069 --- /dev/null +++ b/otp/distributed/AstronAccountAI.py @@ -0,0 +1,5 @@ +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.DistributedObjectAI import DistributedObjectAI + +class AstronAccountAI(DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory('AstronAccountAI') diff --git a/otp/distributed/AstronAccountUD.py b/otp/distributed/AstronAccountUD.py new file mode 100644 index 0000000..94e8539 --- /dev/null +++ b/otp/distributed/AstronAccountUD.py @@ -0,0 +1,5 @@ +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.DistributedObjectUD import DistributedObjectUD + +class AstronAccountUD(DistributedObjectUD): + notify = DirectNotifyGlobal.directNotify.newCategory('AstronAccountUD') diff --git a/otp/login/AstronLoginManagerUD.py b/otp/login/AstronLoginManagerUD.py index dd05b3f..4e40912 100644 --- a/otp/login/AstronLoginManagerUD.py +++ b/otp/login/AstronLoginManagerUD.py @@ -1,34 +1,179 @@ +import anydbm +import dumbdbm +import sys +import time + from direct.directnotify import DirectNotifyGlobal from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD from direct.distributed.PyDatagram import * -class AstronLoginManagerUD(DistributedObjectGlobalUD): - notify = DirectNotifyGlobal.directNotify.newCategory('AstronLoginManagerUD') +class AccountDB: + """ + AccountDB is the base class for all account database interface implementations. + """ + notify = DirectNotifyGlobal.directNotify.newCategory('AccountDB') - def requestLogin(self, playToken): - # TODO SET THIS UP PROPERLY. - # AT THE MOMENT EVERYTHING IS HARDCODED - # THIS IS JUST TO GET TO THE PICK A TOON SCREEN + def __init__(self, loginManager): + self.loginManager = loginManager - # get the sender - sender = self.air.getMsgSender() + # Setup the dbm: + accountDbFile = config.GetString('accountdb-local-file', 'astron/databases/accounts.db') + if sys.platform == 'darwin': # macOS + dbm = dumbdbm + else: + dbm = anydbm + + self.dbm = dbm.open(accountDbFile, 'c') + + def lookup(self, playToken, callback): + raise NotImplementedError('lookup') # Must be overridden by subclass. + + def storeAccountId(self, databaseId, accountId, callback): + self.dbm[databaseId] = str(accountId) + if hasattr(self.dbm, 'sync') and self.dbm.sync: + self.dbm.sync() + callback(True) + else: + self.notify.warning('Unable to associate user %s with account %s!' % (databaseId, accountId)) + callback(False) + + +class DeveloperAccountDB(AccountDB): + notify = DirectNotifyGlobal.directNotify.newCategory('DeveloperAccountDB') + + def lookup(self, playToken, callback): + # Check if this play token exists in the dbm: + if str(playToken) not in self.dbm: + # It is not, so we'll associate them with a brand new account object. + callback({'success': True, + 'accountId': 0, + 'databaseId': playToken}) + else: + # We already have an account object, so we'll just return what we have. + callback({'success': True, + 'accountId': int(self.dbm[playToken]), + 'databaseId': playToken}) + + +class LoginOperation: + notify = DirectNotifyGlobal.directNotify.newCategory('LoginOperation') + + def __init__(self, loginManager, sender): + self.loginManager = loginManager + self.sender = sender + self.playToken = '' + self.databaseId = 0 + self.accountId = 0 + self.account = None + + def start(self, playToken): + self.playToken = playToken + self.loginManager.accountDb.lookup(playToken, self.__handleLookup) + + def __handleLookup(self, result): + if not result.get('success'): + # TODO: Kill the connection + return + + self.databaseId = result.get('databaseId', 0) + accountId = result.get('accountId', 0) + if accountId: + self.accountId = accountId + self.__handleRetrieveAccount() + else: + self.__handleCreateAccount() + + def __handleRetrieveAccount(self): + self.loginManager.air.dbInterface.queryObject(self.loginManager.air.dbId, self.accountId, self.__handleAccountRetrieved) + + def __handleAccountRetrieved(self, dclass, fields): + if dclass != self.loginManager.air.dclassesByName['AstronAccountUD']: + print 'no uwu' + return + + self.account = fields + self.__handleSetAccount() + + def __handleCreateAccount(self): + self.account = {'ACCOUNT_AV_SET': [0] * 6, + 'ESTATE_ID': 0, + 'ACCOUNT_AV_SET_DEL': [], + 'CREATED': time.ctime(), + 'LAST_LOGIN': time.ctime(), + 'ACCOUNT_ID': str(self.databaseId)} + + self.loginManager.air.dbInterface.createObject(self.loginManager.air.dbId, self.loginManager.air.dclassesByName['AstronAccountUD'], self.account, self.__handleAccountCreated) + + def __handleAccountCreated(self, accountId): + if not accountId: + # FAILURE!!!!! + return + + self.accountId = accountId + self.__storeAccountId() + + def __storeAccountId(self): + self.loginManager.accountDb.storeAccountId(self.databaseId, self.accountId, self.__handleAccountIdStored) + + def __handleAccountIdStored(self, success=True): + if not success: + # FAILURE!!!!!!!!!!!! + return + + self.__handleSetAccount() + + def __handleSetAccount(self): + # if somebody's already logged into this account, disconnect them + datagram = PyDatagram() + datagram.addServerHeader(self.loginManager.GetAccountConnectionChannel(self.accountId), self.loginManager.air.ourChannel, CLIENTAGENT_EJECT) + datagram.addUint16(100) + datagram.addString('This account has been logged in elsewhere.') + self.loginManager.air.send(datagram) # add connection to account channel datagram = PyDatagram() - datagram.addServerHeader(sender, self.air.ourChannel, CLIENTAGENT_OPEN_CHANNEL) - datagram.addChannel(self.GetAccountConnectionChannel(1000000000)) - self.air.send(datagram) + datagram.addServerHeader(self.sender, self.loginManager.air.ourChannel, CLIENTAGENT_OPEN_CHANNEL) + datagram.addChannel(self.loginManager.GetAccountConnectionChannel(self.accountId)) + self.loginManager.air.send(datagram) # set sender channel to represent account affiliation datagram = PyDatagram() - datagram.addServerHeader(sender, self.air.ourChannel, CLIENTAGENT_SET_CLIENT_ID) - datagram.addChannel(1000000000 << 32) # accountId is in high 32 bits, 0 in low (no avatar). - self.air.send(datagram) + datagram.addServerHeader(self.sender, self.loginManager.air.ourChannel, CLIENTAGENT_SET_CLIENT_ID) + datagram.addChannel(self.accountId << 32) # accountId is in high 32 bits, 0 in low (no avatar). + self.loginManager.air.send(datagram) # set client state to established, thus un-sandboxing the sender - self.air.setClientState(sender, 2) + self.loginManager.air.setClientState(self.sender, 2) # send dummy login response import json a = json.dumps({}) - self.sendUpdateToChannel(sender, 'loginResponse', [a]) + self.loginManager.sendUpdateToChannel(self.sender, 'loginResponse', [a]) + + +class AstronLoginManagerUD(DistributedObjectGlobalUD): + notify = DirectNotifyGlobal.directNotify.newCategory('AstronLoginManagerUD') + + def __init__(self, air): + DistributedObjectGlobalUD.__init__(self, air) + self.accountDb = None + self.sender2loginOperation = {} + + def announceGenerate(self): + DistributedObjectGlobalUD.announceGenerate(self) + + # Instantiate the account database backend. + # TODO: In the future, add more database interfaces & make this configurable. + self.accountDb = DeveloperAccountDB(self) + + def requestLogin(self, playToken): + # Get the connection ID: + sender = self.air.getMsgSender() + + if sender in self.sender2loginOperation.keys(): + # BAD!!!! + return + + newLoginOperation = LoginOperation(self, sender) + self.sender2loginOperation[sender] = newLoginOperation + newLoginOperation.start(playToken)