PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/src/colobot/server/server.py

https://bitbucket.org/zielmicha/colobot-py
Python | 215 lines | 146 code | 49 blank | 20 comment | 16 complexity | fe877260236b80dd5766a9d40c51deb7 MD5 | raw file
  1. # Copyright (C) 2012, Michal Zielinski <michal@zielinscy.org.pl>
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. import multisock
  16. import multisock.jsonrpc
  17. import threading
  18. import functools
  19. import time
  20. import logging
  21. import os
  22. import json
  23. import colobot.server.db
  24. import colobot.game
  25. from colobot.server.models import Profile
  26. from colobot.server.db import random_string
  27. import g3d.serialize
  28. SHA1_LENGTH = 20 # TODO: move to colobot.common
  29. class Server:
  30. def __init__(self, profile, loader):
  31. self.profile = profile
  32. self.loader = loader
  33. self.serializer = g3d.serialize.Serializer()
  34. self.lock = threading.RLock()
  35. self.game_ticker = g3d.Timer(min_interval=0.05)
  36. self.games = {}
  37. multisock.async(lambda: (multisock.set_thread_name('games'),
  38. self.game_ticker.loop()))
  39. def _init(self, address):
  40. thread = multisock.SocketThread()
  41. acceptor = thread.listen(address)
  42. acceptor.accept.bind(self.accept)
  43. return thread
  44. def run(self, address):
  45. self._init(address).loop()
  46. def start(self, address):
  47. self._init(address).start()
  48. def accept(self, socket):
  49. ConnectionHandler(server=self, profile=self.profile, socket=socket)
  50. # -----------------------------
  51. def create_game(self, name):
  52. if name in self.games:
  53. raise KeyError(name)
  54. game = self.games[name] = colobot.game.Game(self.loader)
  55. self.game_ticker.add_ticker(game.tick)
  56. class ConnectionHandler:
  57. def __init__(self, server, profile, socket):
  58. self.server = server
  59. self.socket = socket
  60. self.profile = profile
  61. self.user = None
  62. self.reset_auth_token()
  63. self.setup_connection()
  64. def reset_auth_token(self):
  65. self.auth_token = random_string(20)
  66. def setup_connection(self):
  67. main = self.socket.get_main_channel()
  68. self.rpc = multisock.jsonrpc.JsonRpcChannel(main, async=True)
  69. self.rpc.server = self
  70. def rpc__getAttributeNames(self):
  71. # for iPython
  72. return [ name[4:] for name in dir(self) if name.startswith('rpc_') ]
  73. def rpc_trait_names(self):
  74. # for iPython
  75. return []
  76. # --------------------------
  77. def rpc_eval(self, code):
  78. if self.user['login'] != 'root':
  79. raise RuntimeError('only root can eval')
  80. if os.environ['COLOBOT_EVAL'] != 'allow':
  81. raise RuntimeError('COLOBOT_EVAL env varible not set to "allow"')
  82. return eval(code, {'server': self.server, 'user': self.user, 'self': self})
  83. def rpc_get_auth_tokens(self, login):
  84. return {'token': self.auth_token,
  85. 'salt': self.profile.users.get_by('login', login)['salt'] if login else None}
  86. def rpc_authenticate(self, login, password_token):
  87. self.user = self.profile.users.authenticate(self.auth_token, login, password_token)
  88. self.reset_auth_token()
  89. return self.profile.sessions.create_session(self.user['login'])
  90. def rpc_use_session(self, uid):
  91. name = self.profile.sessions.get_session(uid)
  92. self.user = self.profile.users.get_by('login', name)
  93. def rpc_passwd(self, login, password_token, salt):
  94. if login != self.user.login:
  95. self.user.check_permission('manage-users')
  96. self.profile.users.get_by('name', login).change_password(password=password_token, salt=salt)
  97. def rpc_list_games(self):
  98. return self.server.games.keys()
  99. def rpc_create_game(self, name):
  100. self.user.check_permission('create-games')
  101. self.server.create_game(name)
  102. def rpc_get_dependencies(self, objects_sha1):
  103. l = []
  104. for object_sha1 in objects_sha1:
  105. l += self.server.serializer.get_dependencies_by_sha1(object_sha1.decode('hex'))
  106. return [ ident.encode('hex') for ident in l ]
  107. def rpc_get_resources(self, identifiers):
  108. channel = self.socket.new_channel()
  109. for ident in identifiers:
  110. ident = ident.decode('hex')
  111. assert type(ident) == str and len(ident) == SHA1_LENGTH, repr(ident)
  112. data = self.server.serializer.get_by_sha1(ident)
  113. channel.send_async(ident + data)
  114. return channel.id
  115. # ---- GAME -----
  116. def rpc_load_terrain(self, game_name, name):
  117. self.user.check_game_permission(game_name, 'manage')
  118. self.server.games[game_name].load_terrain(name)
  119. def rpc_get_terrain(self, game_name):
  120. terrain = self.server.games[game_name].terrain
  121. return self.server.serializer.add(terrain).encode('hex')
  122. def rpc_open_update_channel(self, game_name):
  123. channel = self.socket.new_channel()
  124. handler = UpdateChannelHandler(channel, self.server, self.server.games[game_name])
  125. multisock.async(handler.loop)
  126. return channel.id
  127. def rpc_create_static_object(self, game_name, model_name):
  128. self.user.check_game_permission(game_name, 'manage')
  129. self.server.games[game_name].create_static_object(self.user.login, model_name)
  130. def rpc_get_user_objects(self, game_name):
  131. return [ object.ident for object in
  132. self.server.games[game_name].get_player_objects(self.user.login) ]
  133. def rpc_motor(self, game_name, bot_id, motor):
  134. self.server.games[game_name].motor(self.user.login, bot_id, motor)
  135. def rpc_load_scene(self, game_name, scene_name):
  136. self.user.check_game_permission(game_name, 'manage')
  137. self.server.games[game_name].load_scene(scene_name)
  138. class UpdateChannelHandler(object):
  139. def __init__(self, channel, server, game):
  140. self.channel = channel
  141. self.game = game
  142. self.server = server
  143. self.last_objects = set()
  144. def loop(self):
  145. multisock.set_thread_name('update sender')
  146. timer = g3d.Timer(min_interval=0.1)
  147. timer.add_ticker(self.tick)
  148. timer.loop()
  149. def tick(self, _):
  150. # TODO: use denser format
  151. objects = set(self.game.get_objects())
  152. new_objects = objects - self.last_objects
  153. deleted_objects = self.last_objects - objects
  154. updates = []
  155. updates_time = time.time()
  156. for obj in objects:
  157. updates.append((obj.ident, obj.position, obj.velocity, obj.rotation, None))
  158. data = (
  159. updates_time,
  160. [ (obj.ident, self.server.serializer.add(obj.model)) for obj in new_objects ],
  161. [ obj.ident for obj in deleted_objects ],
  162. updates,
  163. )
  164. blob = self.server.serializer.serialize(data)
  165. self.channel.send(blob)
  166. self.last_objects = objects