nitrpg: migrate data management from `json_store` to `mongodb`
authorAlexandre Terrasa <alexandre@moz-code.org>
Thu, 25 Jun 2015 02:40:43 +0000 (22:40 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Thu, 3 Dec 2015 04:09:01 +0000 (23:09 -0500)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

contrib/nitrpg/src/achievements.nit
contrib/nitrpg/src/events.nit
contrib/nitrpg/src/game.nit
contrib/nitrpg/src/statistics.nit
contrib/nitrpg/src/templates/templates_base.nit

index 5ce317a..c1a8036 100644 (file)
@@ -32,22 +32,28 @@ redef class GameEntity
        #
        # TODO should update the achievement?
        fun add_achievement(achievement: Achievement) do
-               var key = self.key / achievement.key
-               if game.store.has_key(key) then return
                stats.inc("achievements")
-               achievement.save_in(self.key)
-               save
+               achievement.owner = self
+               achievement.save
        end
 
+       # Is `a` unlocked for this `Player`?
+       fun has_achievement(a: Achievement): Bool do return load_achievement(a.id) != null
+
        # Load the event from its `id`.
        #
        # Looks for the event save file in game data.
        # Returns `null` if the event cannot be found.
        fun load_achievement(id: String): nullable Achievement do
-               var key = self.key / "achievements" / id
-               if not game.store.has_key(key) then return null
-               var json = game.store.load_object(key)
-               return new Achievement.from_json(game, json)
+               var req = new JsonObject
+               req["id"] = id
+               req["game"] = game.key
+               req["owner"] = key
+               var obj = game.db.collection("achievements").find(req)
+               if obj isa JsonObject then
+                       return new Achievement.from_json(game, obj)
+               end
+               return null
        end
 
        # List all events registered in this entity.
@@ -56,12 +62,13 @@ redef class GameEntity
        #
        # To add events see `add_event`.
        fun load_achievements: MapRead[String, Achievement] do
+               var req = new JsonObject
+               req["game"] = game.key
+               req["owner"] = key
                var res = new HashMap[String, Achievement]
-               var key = self.key / "achievements"
-               if not game.store.has_collection(key) then return res
-               var coll = game.store.list_collection(key)
-               for id in coll do
-                       res[id.to_s] = load_achievement(id.to_s).as(not null)
+               for obj in game.db.collection("achievements").find_all(req) do
+                       var achievement = new Achievement.from_json(game, obj)
+                       res[achievement.id] = achievement
                end
                return res
        end
@@ -74,10 +81,11 @@ end
 class Achievement
        super GameEntity
 
+       redef var collection_name = "achievements"
 
        redef var game
 
-       redef var key is lazy do
+       redef fun key do
                var owner = self.owner
                if owner == null then return id
                return "{owner.key}-{id}"
@@ -121,12 +129,6 @@ class Achievement
 end
 
 redef class Player
-
-       # Is `a` unlocked for this `Player`?
-       fun has_achievement(a: Achievement): Bool do
-               return load_achievement(a.id) != null
-       end
-
        # Unlocks an achievement for this Player based on a GithubEvent.
        #
        # Register the achievement and adds the achievement reward to the player
@@ -140,6 +142,7 @@ redef class Player
                nitcoins += a.reward
                add_achievement(a)
                trigger_unlock_event(a, event)
+               save
        end
 
        # Create a new event that marks the achievement unlocking.
index bd1ee58..2573a58 100644 (file)
@@ -35,13 +35,12 @@ redef class GameEntity
        #
        # To add events see `add_event`.
        fun load_events: Array[GameEvent] do
-               var key = key / "events"
+               var req = new JsonObject
+               req["game"] = game.key
+               req["owner"] = key
                var res = new Array[GameEvent]
-               if not game.store.has_collection(key) then return res
-               var coll = game.store.list_collection(key)
-               for id in coll do
-                       var name = id.to_s
-                       res.add load_event(name).as(not null)
+               for obj in game.db.collection("events").find_all(req) do
+                       res.add new GameEvent.from_json(game, obj)
                end
                (new EventTimeComparator).sort(res)
                return res
@@ -52,10 +51,13 @@ redef class GameEntity
        # Looks for the event save file in game data.
        # Returns `null` if the event cannot be found.
        fun load_event(id: String): nullable GameEvent do
-               var key = key / "events" / id
-               if not game.store.has_key(key) then return null
-               var json = game.store.load_object(key)
-               return new GameEvent.from_json(game, json)
+               var req = new JsonObject
+               req["game"] = game.key
+               req["owner"] = key
+               req["internal_id"] = id
+               var res = game.db.collection("events").find(req)
+               if res != null then return new GameEvent.from_json(game, res)
+               return null
        end
 end
 
@@ -63,6 +65,7 @@ end
 class GameEvent
        super GameEntity
 
+       redef var collection_name = "events"
 
        redef var game
 
index 2924e46..9b550c3 100644 (file)
@@ -25,7 +25,7 @@
 # the `GameReactor` abstraction.
 module game
 
-intrude import json::store
+import mongodb
 import github::events
 
 # An entity within a `Game`.
@@ -35,23 +35,21 @@ interface GameEntity
        # The game instance containing `self`.
        fun game: Game is abstract
 
-       # Uniq key used for data storage.
-       fun key: String is abstract
+       # Collection `self` should be saved in.
+       fun collection_name: String is abstract
 
-       # Saves the `self` state in game data.
-       #
-       # Date are stored under `self.key`.
-       fun save do game.store.store_object(key, to_json)
+       # Uniq key of this entity within the collection.
+       fun key: String is abstract
 
-       # Saves `self` state under `key` data.
-       #
-       # Data are stored under `key / self.key`.
-       fun save_in(key: String) do
-               game.store.store_object(key / self.key, to_json)
-       end
+       # Saves `self` in db.
+       fun save do game.db.collection(collection_name).save(to_json)
 
        # Json representation of `self`.
-       fun to_json: JsonObject  do return new JsonObject
+       fun to_json: JsonObject do
+               var json = new JsonObject
+               json["_id"] = key
+               return json
+       end
 
        # Pretty print `self` to be displayed in a terminal.
        fun pretty: String is abstract
@@ -74,18 +72,29 @@ class Game
        # Game name
        var name: String = repo.full_name is lazy
 
-       # Directory where game data are stored.
-       var game_dir: String is lazy do return "nitrpg_data" / repo.full_name
-
        redef var key = name is lazy
 
-       # Used for data storage.
-       #
-       # File are stored in `game_dir`.
-       var store: JsonStore is lazy do return new JsonStore(game_dir)
+       # Mongo server url where this game data are stored.
+       var mongo_url = "mongodb://localhost:27017" is writable
+
+       # Mongo db client.
+       var client = new MongoClient(mongo_url) is lazy
+
+       # Mongo db name where this game data are stored.
+       var db_name = "nitrpg" is writable
+
+       # Mongo db instance for this game.
+       var db: MongoDb is lazy do return client.database(db_name)
+
+       redef var collection_name = "games"
 
        # Init the Game and try to load saved data.
-       init do if store.has_key(key) then from_json(store.load_object(key))
+       init do
+               var req = new JsonObject
+               req["name"] = repo.full_name
+               var res = db.collection("games").find(req)
+               if res != null then from_json(res)
+       end
 
        # Init `self` from a JsonObject.
        #
@@ -118,10 +127,12 @@ class Game
        # Returns `null` if the player cannot be found.
        # In this case, the player can be created with `add_player`.
        fun load_player(name: String): nullable Player do
-               var key = "players" / name
-               if not store.has_key(key) then return null
-               var json = store.load_object(key)
-               return new Player.from_json(self, json)
+               var req = new JsonObject
+               req["name"] = name
+               req["game"] = game.key
+               var res = db.collection("players").find(req)
+               if res != null then return new Player.from_json(self, res)
+               return null
        end
 
        # List known players.
@@ -130,12 +141,12 @@ class Game
        #
        # To add players see `add_player`.
        fun load_players: MapRead[String, Player] do
+               var req = new JsonObject
+               req["game"] = game.key
                var res = new HashMap[String, Player]
-               if not store.has_collection("players") then return res
-               var coll = store.list_collection("players")
-               for id in coll do
-                       var name = id.to_s
-                       res[name] = load_player(name).as(not null)
+               for obj in db.collection("players").find_all(req) do
+                       var player = new Player.from_json(self, obj)
+                       res[player.name] = player
                end
                return res
        end
@@ -154,7 +165,7 @@ class Game
        end
 
        # Erase all saved data for this game.
-       fun clear do store.clear
+       fun clear do db.collection(collection_name).remove(to_json)
 
        # Verbosity level used fo stdout.
        #
@@ -186,6 +197,9 @@ end
 class Player
        super GameEntity
 
+       # Stored in collection `players`.
+       redef var collection_name = "players"
+
        redef var game
 
        # FIXME contructor should be private
index 153b5cd..0bdb421 100644 (file)
@@ -34,11 +34,6 @@ redef class Game
 
        redef var stats is lazy do return new GameStatsManager(game, self)
 
-       redef fun save do
-               super
-               stats.save_in(self.key)
-       end
-
        redef fun pretty do
                var res = new FlatBuffer
                res.append super
@@ -46,17 +41,17 @@ redef class Game
                res.append stats.pretty
                return res.write_to_string
        end
+
+       redef fun save do
+               super
+               stats.save
+       end
 end
 
 redef class Player
 
        redef var stats is lazy do return new GameStatsManager(game, self)
 
-       redef fun save do
-               super
-               stats.save_in(self.key)
-       end
-
        redef fun nitcoins do return stats["nitcoins"]
        redef fun nitcoins=(nc) do stats["nitcoins"] = nc
 
@@ -67,6 +62,11 @@ redef class Player
                res.append stats.pretty
                return res.write_to_string
        end
+
+       redef fun save do
+               super
+               stats.save
+       end
 end
 
 # Store game stats for defined period.
@@ -99,12 +99,15 @@ class GameStatsManager
 
        # Load statistics for a `period` key.
        fun load_stats_for(period: String): GameStats do
-               var key = owner.key / self.key / period
-               if not game.store.has_key(key) then
+               var req = new JsonObject
+               req["period"] = period
+               req["owner"] = owner.key
+               var obj = game.db.collection("statistics").find(req)
+               if obj isa JsonObject then
+                       return new GameStats.from_json(game, period, owner, obj)
+               else
                        return new GameStats(game, period, owner)
                end
-               var json = game.store.load_object(key)
-               return new GameStats.from_json(game, period, owner, json)
        end
 
        redef fun [](key) do return overall[key]
@@ -133,12 +136,12 @@ class GameStatsManager
                weekly.dec(e)
        end
 
-       redef fun save_in(key) do
-               overall.save_in(key / self.key)
-               yearly.save_in(key / self.key)
-               monthly.save_in(key / self.key)
-               daily.save_in(key / self.key)
-               weekly.save_in(key / self.key)
+       redef fun save do
+               overall.save
+               yearly.save
+               monthly.save
+               daily.save
+               weekly.save
        end
 
        redef fun pretty do return overall.pretty
@@ -151,6 +154,8 @@ class GameStats
 
        redef var game
 
+       redef var collection_name = "statistics"
+
        # The period these stats are about.
        var period: String
 
index a4f40d9..470160b 100644 (file)
@@ -21,8 +21,11 @@ import achievements
 
 redef class GameEntity
 
+       # Path to this entity from root.
+       fun path: String do return collection_name / key
+
        # URL to this game entity page.
-       fun url: String do return game.url / key
+       fun url: String do return game.url / path
 end
 
 redef class Game
@@ -32,7 +35,7 @@ redef class Game
        # This must be set before any access to `url`.
        var root_url: String is noinit, writable
 
-       redef fun url do return "{root_url}/games" / key
+       redef fun url do return "{root_url}/{path}"
 
        # Return a HTML link to this Game.
        fun link: String do return "<a href=\"{url}\">{name}</a>"