Passed
Branch master (994cf3)
by Marek
01:46
created

ige.BookingMngr.Booking.set_password()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
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
import hashlib
22
import os
23
import random
24
import time
25
26
import data
27
import ige
28
import log
29
from ige.ospace import Const
30
from ige.ospace import Utils
31
32
from ige import GameException, BookingMngrException
33
from ige.Transaction import Transaction
34
from ige.IDataHolder import IDataHolder
35
from ige.ospace.GalaxyGenerator import GalaxyGenerator
36
37
class Booking(object):
38
    def __init__(self, gal_type):
39
        self.players = set([])
40
        self.gal_type = gal_type
41
        self.last_creation = None
42
        self.capacity = None
43
        self.owner = None
44
        self.owner_nick = None
45
        self.pw_salt = hashlib.sha256(str(random.random())).hexdigest()
46
        self.pw_hash = None
47
48
    def toggle_booking(self, player):
49
        try:
50
            self.players.remove(player)
51
        except KeyError:
52
            self.players.add(player)
53
        return self.is_filled()
54
55
    def discard_booking(self, player):
56
        self.players.discard(player)
57
58
    def is_filled(self):
59
        return len(self.players) == self.capacity
60
61
    def set_password(self, password):
62
        self.pw_hash = hashlib.sha256(password + self.pw_salt).hexdigest()
63
64
    def check_password(self, password):
65
        pw_hash = hashlib.sha256(password + self.pw_salt).hexdigest()
66
        return self.pw_hash == pw_hash
67
68
    def answer(self, player):
69
        answer = IDataHolder()
70
        answer.bookings = len(self.players)
71
        answer.last_creation = self.last_creation
72
        answer.is_booked = player in self.players
73
        answer.owner_nick = self.owner_nick
74
        answer.gal_type = self.gal_type
75
        answer.capacity = self.capacity
76
        return answer
77
78
class BookingMngr(object):
79
    def __init__(self, clientMngr, gameMngr, db):
80
        self.clientMngr = clientMngr
81
        self.gameMngr = gameMngr
82
        self.db = db
83
        self.db.nextID = ige.Const.BID_FREESTART
84
        self.offerings = GalaxyGenerator().templates
85
        self.init_bookings()
86
87
    def _create_booking(self, gal_type):
88
        book = Booking(gal_type)
89
        if self.offerings[gal_type].scenario == Const.SCENARIO_SINGLE:
90
            # hardcoded 1, as some galaxies has rebels in them
91
            book.capacity = 1
92
        else:
93
            book.capacity = self.offerings[gal_type].players
94
        return book
95
96
    def init_bookings(self):
97
        for gal_type in self.offerings:
98
            bookings = self._get_type_bookings(gal_type)
99
            if not bookings:
100
                book = self._create_booking(gal_type)
101
                self.db.create(book)
102
                bookings.append(book)
103
104
        # cleanup of those not used anymore
105
        for bookID in self.db.keys():
106
            gal_type = self.db[bookID].gal_type
107
            if gal_type not in self.offerings:
108
                del self.db[bookID]
109
110
    def shutdown(self):
111
        log.message('Shutdown')
112
        self.db.shutdown()
113
114
    def checkpoint(self):
115
        self.db.checkpoint()
116
117
    def clear(self):
118
        self.db.clear()
119
120
    def backup(self, basename):
121
        self.db.backup(basename)
122
123
    def upgrade(self):
124
        return
125
126
    def _get_type_bookings(self, gal_type):
127
        bookings = []
128
        for bookID in self.db.keys():
129
            book = self.db[bookID]
130
            if book.gal_type == gal_type:
131
                bookings.append(book)
132
        return bookings
133
134
    def _get_challenges(self, template):
135
        challenges = []
136
        if template.scenario == Const.SCENARIO_SINGLE and template.players > 1:
137
            challenges.append(Const.T_AIPLAYER)
138
        if set([Const.SR_TL1A, Const.SR_TL1B]).intersection(template.resources):
139
            challenges.append(Const.T_AIRENPLAYER)
140
        if set([Const.DISEASE_MUTANT]).intersection(template.diseases):
141
            challenges.append(Const.T_AIMUTPLAYER)
142
        if set([Const.SR_TL3A, Const.SR_TL3B, Const.SR_TL3C]).intersection(template.resources):
143
            challenges.append(Const.T_AIPIRPLAYER)
144
        if set([Const.SR_TL5A, Const.SR_TL5B, Const.SR_TL5C]).intersection(template.resources):
145
            challenges.append(Const.T_AIEDENPLAYER)
146
        return challenges
147
148
    def _create_answer(self, gal_type):
149
        template = self.offerings[gal_type]
150
        answer = IDataHolder()
151
        answer.scenario = template.scenario
152
        answer.minPlanets = template.minPlanets
153
        answer.maxPlanets = template.maxPlanets
154
        answer.radius = template.radius
155
        answer.players = template.players
156
        answer.resources = template.resources.keys()
157
        answer.challenges = self._get_challenges(template)
158
159
        if not template.startR[0] or not template.startR[1]:
160
            # that means grouping is used to maximize distance between players
161
            # most likely brawl scenario
162
            answer.playerGroup = 1
163
        else:
164
            answer.playerGroup = template.playerGroup
165
        return answer
166
167
    def _is_valid_offer(self, sid, gal_type):
168
        hideSingle = self.gameMngr.singleGamesLimit(sid)
169
        template = self.offerings[gal_type]
170
        if gal_type == "Test":
171
            return False
172
        if hideSingle and template.scenario == Const.SCENARIO_SINGLE:
173
            return False
174
        return True
175
176
    def get_booking_offers(self, sid):
177
        answers = {}
178
        for gal_type in self.offerings:
179
            if not self._is_valid_offer(sid, gal_type):
180
                continue
181
            answers[gal_type] = self._create_answer(gal_type)
182
        return answers, None
183
184
    def _get_booking_answers(self, sid):
185
        login = self.clientMngr.getSession(sid).login
186
        answers = {}
187
        for bookID in self.db.keys():
188
            book = self.db[bookID]
189
            if not self._is_valid_offer(sid, book.gal_type):
190
                continue
191
            answers[bookID] = book.answer(login)
192
        return answers
193
194
    def get_booking_answers(self, sid):
195
        return self._get_booking_answers(sid), None
196
197
    def toggle_booking(self, sid, bookID, password):
198
        session = self.clientMngr.getSession(sid)
199
        book = self.db[bookID]
200
        template = self.offerings[book.gal_type]
201
        if book.owner:
202
            # private booking
203
            if book.owner == session.login:
204
                raise BookingMngrException('Owners cannot toggle their own private bookings.')
205
            if not book.check_password(password):
206
                raise BookingMngrException('Incorrect password.')
207
        if not self._is_valid_offer(sid, book.gal_type):
208
            # limit reached, no toggling allowed
209
            raise BookingMngrException('Cannot toggle, limit of single player galaxies reached?')
210
        log.debug("Player '{0}' toggled booking of '{1}/{2}'".format(session.login, book.gal_type, book.owner))
211
        triggered = False
212
        if book.toggle_booking(session.login):
213
            self.trigger_galaxy_start(bookID)
214
            triggered = True
215
        answers = self._get_booking_answers(sid)
216
        answers[ige.Const.BID_TRIGGERED] = triggered
217
        return answers, None
218
219
    def _private_bookings_limit(self, login):
220
        no_books = 0
221
        for bookID in self.db.keys():
222
            book = self.db[bookID]
223
            if book.owner == login:
224
                no_books += 1
225
        return no_books >= Const.BOOKING_PRIVATE_LIMIT
226
227
    def create_private_booking(self, sid, bookID, password):
228
        session = self.clientMngr.getSession(sid)
229
        if not password:
230
            raise BookingMngrException('Password is needed for private bookings.')
231
        sourceBook = self.db[bookID]
232
        scenario = self.offerings[sourceBook.gal_type].scenario
233
        if scenario == Const.SCENARIO_SINGLE:
234
            raise BookingMngrException('Single scenarios cannot be privately booked.')
235
        if self._private_bookings_limit(session.login):
236
            raise BookingMngrException('Limit of private bookings reached.')
237
        book = self._create_booking(sourceBook.gal_type)
238
        book.owner = session.login
239
        book.owner_nick = session.nick
240
        book.players.add(session.login)
241
        book.set_password(password)
242
        self.db.create(book)
243
        return self.get_booking_answers(sid)
244
245
    def delete_private_booking(self, sid, bookID):
246
        login = self.clientMngr.getSession(sid).login
247
        book = self.db[bookID]
248
        if not book.owner == login:
249
            raise BookingMngrException('Only the owner can delete private booking.')
250
        del self.db[bookID]
251
        return self.get_booking_answers(sid)
252
253
    def trigger_galaxy_start(self, bookID):
254
        book = self.db[bookID]
255
        log.debug("Triggering new subscribed galaxy '{0}'".format(book.gal_type))
256
        # triggers galaxy start regardless booking numbers
257
258
        # TODO: I don't know what I am doing, this seems to be a terrible hack
259
        universe = self.gameMngr.db[ige.Const.OID_UNIVERSE]
260
        tran = Transaction(self.gameMngr, ige.Const.OID_ADMIN)
261
        name = self._find_galaxy_name(book.gal_type, list(book.players))
262
        new_galaxy_ID = self.gameMngr.cmdPool[universe.type].createNewSubscribedGalaxy(tran, universe, name, book.gal_type, list(book.players))
263
264
        if book.owner:
265
            del self.db[bookID]
266
        else:
267
            book.players = set([])
268
            book.last_creation = time.time()
269
270
        return new_galaxy_ID, None
271
272
    def _find_galaxy_name(self, gal_type, players):
273
        scenario = self.offerings[gal_type].scenario
274
        universe = self.gameMngr.db[ige.Const.OID_UNIVERSE]
275
        names_in_use = set([])
276
        # fetch list of used galaxy names
277
        for galaxy_id in universe.galaxies:
278
            galaxy = self.gameMngr.db[galaxy_id]
279
            names_in_use.add(galaxy.name)
280
281
        if scenario == Const.SCENARIO_SINGLE:
282
            player_name = players[0]
283
            name = "{0}-{1}-{2}".format(gal_type, player_name, universe.turn)
284
            if name not in names_in_use:
285
                return name
286
            iterator = 1
287
            while True:
288
                name = "{0}-{1}-{2}.{3}".format(gal_type, player_name, universe.turn, iterator)
289
                if name in names_in_use:
290
                    iterator += 1
291
                    continue
292
                # found new name!
293
                return name
294
        elif scenario in [Const.SCENARIO_COOP, Const.SCENARIO_BRAWL]:
295
            name = "{0}-{1}".format(gal_type, universe.turn)
296
            if name not in names_in_use:
297
                return name
298
            iterator = 1
299
            while True:
300
                name = "{0}-{1}.{2}".format(gal_type, universe.turn, iterator)
301
                if name in names_in_use:
302
                    iterator += 1
303
                    continue
304
                # found new name!
305
                return name
306
        # Outer Space has privilege of having traditional galaxy names
307
        log.debug("Searching for available galaxy name")
308
        all_names = []
309
        for name in open(data.GALAXY_NAMES_FILE):
310
            all_names.append(name.strip())
311
312
        for name in all_names:
313
            if name in names_in_use:
314
                continue
315
            return name
316
        # no name available
317
        return None
318
319