def delete(self, player_id): if player_id != current_user[ "player_id"] and "service" not in current_user["roles"]: abort(httplib.BAD_REQUEST, message="This is not your player") args = self.delete_args.parse_args() force = args.force log.info("Removing player %d from the match queue", player_id) my_matchqueueplayer = g.db.query(MatchQueuePlayer) \ .filter(MatchQueuePlayer.player_id == player_id) \ .order_by(-MatchQueuePlayer.id).first() if not my_matchqueueplayer: abort(httplib.NOT_FOUND, message="Player is not in the queue", code="player_not_in_queue") if my_matchqueueplayer.status == "matched" and not force: abort(httplib.BAD_REQUEST, message="Player has already been matched", code="player_already_matched") g.db.delete(my_matchqueueplayer) g.db.commit() return json_response("Player is no longer in the match queue", httplib.OK)
def get(self, player_id, journal_id): """ Get a specific journal entry for the player """ can_edit_player(player_id) entry = get_journal_entry(player_id, journal_id) if not entry.first(): return json_response("Journal entry not found", httplib.NOT_FOUND) ret = entry.first().as_dict() return ret
def delete(self, client_id): """ Deregister an already registered client. Should return status 200 if successful. """ ret = self.validate_call(client_id) if ret: return ret client = g.db.query(Client).get(client_id) if not client or client.status == "deleted": abort(httplib.NOT_FOUND) client.heartbeat = utcnow() client.num_heartbeats += 1 client.status = "deleted" g.db.commit() log.info("Client %s from player %s has been unregistered", client_id, current_user["player_id"]) return json_response("Client has been closed. Please terminate the client.", httplib.OK)
def post(self, player_id): """ Add a journal entry """ if not can_edit_player(player_id): abort(httplib.METHOD_NOT_ALLOWED, message="That is not your player!") args_list = request.json if not isinstance(args_list, list): raise RuntimeError("Arguments should be a list") for a in args_list: if "journal_id" not in a: abort(httplib.BAD_REQUEST) ret = [] now = datetime.datetime.utcnow() MAX_DRIFT = 60 args_list.sort(key=itemgetter('journal_id')) client_current_time = args_list[0].get("client_current_time") if not client_current_time: log.warning("Client is uploading journal entries without a client_current_time") else: client_current_time = parser.parse(client_current_time) diff = (client_current_time.replace(tzinfo=None) - now).total_seconds() if abs(diff) > MAX_DRIFT: log.warning("Client's clock is %.0f seconds out of sync. " "Client system time: '%s', Server time: '%s'", diff, client_current_time, now) for args in args_list: # Special handling if this is a rollback event. # We mark all journal entries higher than the event to rollback to # as deleted (as an optimization) and then add the rollback event itself. if "rollback_to_journal_id" in args: to_journal_id = int(args.get("rollback_to_journal_id")) # TODO: Check if there are any gamestates persisted after the id gamestate = get_player_gamestate(player_id) if not gamestate: log.warning("Player is rebasing journal entries but doesn't" "have any gamestate.") elif gamestate.journal_id > to_journal_id: return json_response("Journal has already been persisted into home base!", httplib.BAD_REQUEST) entry = get_journal_entry(player_id, to_journal_id) if not entry.first(): log.warning("Rolling back to journal entry %s which doesn't exist") elif entry.first().deleted: log.warning("Rolling back to journal entry %s which has been rolled back") g.db.query(PlayerJournal).filter(PlayerJournal.player_id == player_id, PlayerJournal.journal_id > to_journal_id) \ .update({"deleted": True}) # report if the client's clock is out of sync with the server timestamp = parser.parse(args["timestamp"]) diff = (timestamp.replace(tzinfo=None) - now).total_seconds() if abs(diff) > MAX_DRIFT: log.info("Client is sending journal info for journal entry %s '%s' which " "is %.0f seconds out of sync. " "Client journal timestamp: '%s', Server timestamp: '%s'", args["journal_id"], args["action"], diff, args["timestamp"], now) try: journal = write_journal(player_id, args["action"], args["journal_id"], args["timestamp"], details=args.get("details"), steps=args.get("steps"), actor_id=current_user["player_id"]) except JournalError as e: # TODO: We now reject the journal entry and subsequent entries instead of # rolling the entire thing back. Is that what we want? log.warning("Error writing to journal. Rejecting entry. Error was: %s", e) abort(httplib.BAD_REQUEST, description=e.message) ret.append({"journal_id": journal["journal_id"], "url": url_for("journal.entry", player_id=player_id, journal_id=journal["journal_id"]) }) return ret, httplib.CREATED
def put(self, player_id, namespace): """ Upload the gamestate state to the server """ can_edit_player(player_id) args = request.json data = args["gamestate"] journal_id = None if args.get("journal_id"): journal_id = int(args["journal_id"]) if journal_id: journal_row = g.db.query(PlayerJournal) \ .filter(PlayerJournal.player_id == player_id, PlayerJournal.journal_id == journal_id, PlayerJournal.deleted != True) \ .first() if not journal_row: # Note: this might happen normally unless we serialize on the # client to ensure all journal entries # are acked before sending up the new state msg = "Journal entry %s for player %s not found!" % ( journal_id, player_id) log.warning(msg) return json_response(msg, httplib.BAD_REQUEST) gamestate = g.db.query(GameState)\ .filter(GameState.player_id == player_id, GameState.namespace == namespace) \ .order_by(-GameState.gamestate_id).first() if gamestate: if journal_id and journal_id <= gamestate.journal_id: # TODO: Raise here? log.warning( "Writing a new gamestate with an older journal_id, %s " "than the current one", journal_id, extra=gamestate.as_dict()) gamestate.version += 1 gamestate.data = data gamestate.journal_id = journal_id log.info( "Updated gamestate for player %s to version %s. journal_id = %s", player_id, gamestate.version, journal_id) else: gamestate = GameState(player_id=player_id, data=data, journal_id=journal_id, namespace=namespace) g.db.add(gamestate) g.db.flush() log.info("Added new gamestate for player") # write new gamestate to the history table for safe keeping gamestatehistory_row = GameStateHistory( player_id=player_id, version=gamestate.version, data=gamestate.data, namespace=gamestate.namespace, journal_id=gamestate.journal_id) g.db.add(gamestatehistory_row) g.db.flush() gamestatehistory_id = gamestatehistory_row.gamestatehistory_id gamestate.gamestatehistory_id = gamestatehistory_id g.db.commit() return gamestate.as_dict()