Issues (229)

server/lib/ige/GameMngr.py (2 issues)

1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Outer Space.
5
#
6
#  Outer Space is free software; you can redistribute it and/or modify
7
#  it under the terms of the GNU General Public License as published by
8
#  the Free Software Foundation; either version 2 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Outer Space is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  GNU General Public License for more details.
15
#
16
#  You should have received a copy of the GNU General Public License
17
#  along with Outer Space; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
# Game manager
22
# Multiple instances can be created (one for each game)
23
import os
24
import os.path
25
import time
26
import random, hashlib # TODO: remove after 0.5.74 release
27
28
import ige
29
import log
30
import Const
31
32
from SQLiteDatabase import Database
33
from Index import Index
34
from IObject import IDataHolder
35
from Transaction import Transaction
36
37
class GameMngr:
38
39
    def __init__(self, gameID, config, clientMngr, msgMngr, database, configDir, gameName = None):
40
        log.debug("Runtime mode", ige.igeRuntimeMode)
41
        self.status = Const.GS_INIT
42
        self.gameID = gameID
43
        self.gameName = gameName or gameID
44
        self.clientMngr = clientMngr
45
        self.msgMngr = msgMngr
46
        self.cmdPool = {}
47
        self.db = database
48
        self.config = config
49
        self.configDir = configDir
50
        # register command objects
51
        # None here
52
53
    def init(self):
54
        pass
55
56
    def start(self):
57
        if self.status == Const.GS_RUNNING:
58
            return
59
        # start timer
60
        self.status = Const.GS_RUNNING
61
62
    def stop(self, checkpoint = 1):
63
        if self.status == Const.GS_STOPPED:
64
            return
65
        # stop timer
66
        self.status = Const.GS_STOPPED
67
        if checkpoint:
68
            self.db.checkpoint()
69
            self.msgMngr.checkpoint()
70
            self.clientMngr.checkpoint()
71
72
    def shutdown(self):
73
        if self.status == Const.GS_SDOWN:
74
            return
75
        self.stop(checkpoint = 0)
76
        self.status = Const.GS_SDOWN
77
        self.db.shutdown()
78
79
    def upgrade(self):
80
        oldStatus = self.status
81
        self.status = Const.GS_MAINT
82
        tran = Transaction(self, Const.OID_ADMIN)
83
        # used objects
84
        objIDs = {}
85
        for objID in self.db.keys():
86
            objIDs[objID] = None
87
        del objIDs[1]
88
        del objIDs[Const.OID_ADMIN]
89
        del objIDs[Const.OID_I_LOGIN2OID]
90
        # stats
91
        types = {}
92
        typesMin = {}
93
        typesMax = {}
94
        typesSum = {}
95
        # TODO: remove after 0.5.74
96
        # hash passwords in database
97
        for accountID in self.clientMngr.accounts.keys():
98
            account = self.clientMngr.accounts[accountID]
99
            if hasattr(account, 'passwdHashed'):
100
                continue
101
            if isinstance(account.passwd, unicode):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable unicode does not seem to be defined.
Loading history...
102
                account.passwd = account.passwd.encode('utf-8')
103
            elif not isinstance(account.passwd, str):
104
                # unexpected!
105
                raise TypeError
106
            # hash passwords of normal players
107
            if account.isAI or account.login == Const.ADMIN_LOGIN:
108
                account.passwdHashed = False
109
            else:
110
                account.passwdHashed = True
111
                account.setPassword(account.passwd)
112
        # upgrade all objects in database
113
        # and collect all not referenced objects
114
        for id in self.db.keys():
115
            try:
116
                obj = self.db[id]
117
            except:
118
                log.warning("Cannot upgrade object", id, "no such id in db")
119
            if not isinstance(obj, IDataHolder):
120
                #@log.debug('Upgrade - skiping', id)
121
                continue
122
            #@log.debug('Upgrade - upgrading', id, obj.type)
123
            types[obj.type] = types.get(obj.type, 0) + 1
124
            size = self.db.getItemLength(id)
125
            typesMin[obj.type] = min(typesMin.get(obj.type, 1000000), size)
126
            typesMax[obj.type] = max(typesMax.get(obj.type, 0), size)
127
            typesSum[obj.type] = typesSum.get(obj.type, 0) + size
128
            if self.cmdPool.has_key(obj.type):
129
                try:
130
                    self.cmdPool[obj.type].upgrade(tran, obj)
131
                except Exception, e:
132
                    log.warning("Cannot upgrade object", id)
133
            references = self.cmdPool[obj.type].getReferences(tran, obj)
134
            if references:
135
                for tmpID in references:
136
                    if tmpID in objIDs:
137
                        del objIDs[tmpID]
138
        # delete all not referenced objects
139
        for objID in objIDs:
140
            log.debug(objID, "is not referenced, deleting it")
141
            del tran.db[objID]
142
        # print stats
143
        log.debug("*****")
144
        for t in types:
145
            log.debug("Object type %d:" % t)
146
            log.debug("  occurences    : %d" % types[t])
147
            log.debug("  size interval : %d - %d bytes" % (typesMin[t], typesMax[t]))
148
            log.debug("  total size    : %d (avg %d) bytes" % (typesSum[t], typesSum[t] / types[t]))
149
        self.status = oldStatus
150
151
    def reset(self):
152
        # cleanup database
153
        self.db.clear()
154
        self.msgMngr.clear()
155
        # create indexes
156
        self.db.create(Index(), Const.OID_I_LOGIN2OID)
157
        # create admin
158
        self.registerPlayer(Const.ADMIN_LOGIN, self.createAdmin(), Const.OID_ADMIN)
159
        # create universe
160
        self.createUniverse()
161
        # save all informations
162
        self.db.checkpoint()
163
        self.msgMngr.checkpoint()
164
        self.clientMngr.checkpoint()
165
166
    def processTurn(self, sid, turns = 1):
167
        session = self.clientMngr.getSession(sid)
168
        if session.login != Const.ADMIN_LOGIN:
169
            raise ige.SecurityException('You cannot issue this command.')
170
        for turn in xrange(turns):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
171
            log.message("--- TURN PROCESSING STARTED ---")
172
            # commit player's changes
173
            #if ige.igeRuntimeMode:
174
            #    self.db.checkpoint()
175
            # get turn phases
176
            turn, turnspec, data = self.getTurnData(sid)[0]
177
            log.debug('Processing turn %d' % turn)
178
            tran = Transaction(self, session.cid, session)
179
            counter = 0
180
            # phases
181
            for objIDs, phases in turnspec:
182
                # process all objects
183
                for objID in objIDs:
184
                    # process all phases
185
                    for phase in phases:
186
                        todo = [objID]
187
                        t0 = time.time()
188
                        cnt0 = self.db.statCount
189
                        log.debug('Processing turn %d phase %d.%s' % (turn, objID, phase))
190
                        while todo:
191
                            tmpID = todo.pop(0)
192
                            #@log.debug('Processing obj', tmpID)
193
                            try:
194
                                counter += 1
195
                                obj = self.db[tmpID]
196
                                method = getattr(self.cmdPool[obj.type], 'process%sPhase' % phase,)
197
                                result = method(tran, obj, data)
198
                                if result:
199
                                    todo.extend(result)
200
                                obj = None
201
                            except:
202
                                log.warning('Cannot execute %s on %d' % (phase, tmpID))
203
                        log.debug('STATS -- time: %.3f sec, db accesses: %d' % (time.time() - t0, tran.db.statCount - cnt0))
204
            log.message('Processed commands:', counter)
205
            # turn processing has finished
206
            self.turnFinished(sid)
207
            log.message("--- TURN PROCESSING FINISHED ---")
208
        return 1, None
209
210
    def getTurnData(self, sid):
211
        # disable command execution during turn processing
212
        self.status = Const.GS_TURNINPROG
213
        return 1, None
214
215
    def turnFinished(self, sid):
216
        # notify logged player's about finished turn
217
        for sessionID in self.clientMngr.sessions.keys():
218
            session = self.clientMngr.getSession(sessionID)
219
            session.messages[Const.SMESSAGE_NEWTURN] = None
220
        # commit only in normal mode
221
        log.debug("Runtime mode", ige.igeRuntimeMode)
222
        if ige.igeRuntimeMode:
223
            self.db.checkpoint()
224
            self.msgMngr.checkpoint()
225
            self.clientMngr.checkpoint()
226
        # enable normal operations
227
        self.status = Const.GS_RUNNING
228
        return 1, None
229
230
    def backup(self, sid, basename):
231
        session = self.clientMngr.getSession(sid)
232
        if session.login != Const.ADMIN_LOGIN:
233
            raise ige.SecurityException('You cannot issue this command.')
234
        self.db.backup(basename)
235
        self.clientMngr.backup(basename)
236
        self.msgMngr.backup(basename)
237
        return True, None
238
239
    def commitDatabases(self, sid):
240
        session = self.clientMngr.getSession(sid)
241
        if session.login != Const.ADMIN_LOGIN:
242
            raise ige.SecurityException('You cannot issue this command.')
243
        self.db.checkpoint()
244
        self.clientMngr.checkpoint()
245
        self.msgMngr.checkpoint()
246
        return True, None
247
248
    def createAdmin(self):
249
        """ Return Player object which will act as administrator of the game."""
250
        raise NotImplementedError
251
252
    def createUniverse(self):
253
        """ Create gaming universe. """
254
        raise NotImplementedError
255
256
    def createPlayer(self, sid, *args, **kwargs):
257
        raise NotImplementedError
258
259
    def removePlayer(self, sid, *args, **kwargs):
260
        raise NotImplementedError
261
262
    def selectPlayer(self, sid, playerID):
263
        """ Selects which of the player objects of the account is going to be
264
        used for this particular session."""
265
266
        session = self.clientMngr.getSession(sid)
267
        if session.cid:
268
            raise ige.GameException('You already selected a player object.')
269
        try:
270
            accounts_player_objects = self.db[Const.OID_I_LOGIN2OID].get(session.login, [])
271
        except AttributeError:
272
            raise ige.SecurityException('Not logged in.')
273
274
        if playerID not in accounts_player_objects:
275
            raise ige.NoAccountException('Player object not on this account.')
276
277
        log.debug('Adding cid to session', playerID)
278
        session.cid = playerID
279
        # validate client
280
        if not self.validateClient(session):
281
            raise ige.GameException('Wrong version of client.')
282
        # notify object, that player has logged in
283
        player = self.db[playerID]
284
        self.cmdPool[player.type].loggedIn(Transaction(self), player)
285
        return True, None
286
287
    def registerPlayer(self, login, playerObj, oid = None, force = 0):
288
        raise NotImplementedError
289
290
    def unregisterPlayer(self, playerObj):
291
        log.debug('unregisterPlayer', playerObj.login, playerObj.name)
292
        # preconditions
293
        if not self.db[Const.OID_I_LOGIN2OID].has_key(playerObj.login):
294
            log.debug("Account %s does not exist" % playerObj.login)
295
        # try to remove it
296
        try:
297
            self.db[Const.OID_I_LOGIN2OID][playerObj.login].remove(playerObj.oid)
298
        except:
299
            log.warning("Cannot remove '%s' from LOGIN2OID index" % playerObj.login)
300
        try:
301
            self.db.delete(playerObj.oid)
302
        except:
303
            log.warning("Cannot remove player %d from database" % playerObj.oid)
304
305
    def validateClient(self, session):
306
        raise NotImplementedError
307
308
    def registerObject(self, cls):
309
        cmdObj = cls(self)
310
        self.cmdPool[cmdObj.typeID] = cmdObj
311
312
    def sendMessage(self, tran, sourceID, msgID, locationID, turn, data):
313
        #@log.debug('Message', sourceID, msgID, locationID, turn, data)
314
        obj = self.db[sourceID]
315
        # notify owner
316
        if obj.owner == Const.OID_NONE:
317
            log.warning('OID', sourceID, 'has no owner - no target for a message')
318
        else:
319
            owner = self.db[obj.owner]
320
            # new style messages
321
            message = {
322
                "sender": obj.name,
323
                "senderID": sourceID,
324
                "forum": "EVENTS",
325
                "data": (sourceID, msgID, locationID, turn, data),
326
                "topic": "EVENT",
327
            }
328
            self.cmdPool[owner.type].sendAdminMsg(tran, owner, message)
329
            session = self.clientMngr.getSessionByCID(obj.owner)
330
            if session:
331
                session.messages[Const.SMESSAGE_NEWMESSAGE] = None
332
333
    # dispatch command
334
    def execute(self, sid, command, oid, *args):
335
        #@startTime = time.time()
336
        log.debug('execute', sid, oid, command, args)
337
        # check client id
338
        session = self.clientMngr.getSession(sid)
339
        if not session.cid:
340
            raise ige.SecurityException('No player object selected.')
341
        if not self.validateClient(session):
342
            raise ige.GameException('Wrong version of client.')
343
        # check game status (admin is allowed anytime)
344
        if self.status != Const.GS_RUNNING and session.cid != Const.OID_ADMIN:
345
            raise ige.ServerStatusException(self.status)
346
        # check existence of the commander
347
        if not self.db.has_key(session.cid):
348
            raise ige.GameException('This player does not exist. He/she could lose.')
349
        # update client's liveness
350
        session.touch()
351
        # find correct object type
352
        try:
353
            obj = self.db[oid]
354
        except KeyError:
355
            raise ige.NoSuchObjectException('Object %d does not exist.' % oid)
356
        cmdObj = getattr(self.cmdPool[obj.type], command)
357
        if not hasattr(cmdObj, 'public') or not cmdObj.public:
358
            raise ige.SecurityException('Access denied - method is not public.')
359
        # get acces level of the commander
360
        accLevel = Const.AL_NONE
361
        if obj.owner == session.cid:
362
            accLevel = Const.AL_OWNER
363
        if session.cid == Const.OID_ADMIN:
364
            accLevel = Const.AL_ADMIN
365
        #@log.debug('access rights', accLevel, cmdObj.accLevel)
366
        if cmdObj.accLevel > accLevel:
367
            raise ige.SecurityException('Access denied - low access level.')
368
        # create transaction (TODO - cache it!)
369
        tran = Transaction(self, session.cid, session)
370
        # invoke command on it
371
        result = cmdObj(*(tran, obj) + args)
372
        # commit transaction
373
        tran.commit()
374
        #@log.debug('result', result)
375
        # session messages
376
        #@log.debug('Messages:', session.messages.items())
377
        messages = session.messages.items()
378
        session.messages.clear()
379
        #@log.debug("Execution time", time.time() - startTime)
380
        return result, messages
381
382