X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Futils%2Fstorage.js;h=2e7a84fb421d91bb5894760914d70cebab6dfb69;hb=d4036efea5b57656478affd7d71f53dcea0f8017;hp=bb7fdce7dd3aca1d0c372f8f284303aa24494431;hpb=4fe5664d48e37cf7e0de7562e8e76e8698b0ea74;p=vchess.git diff --git a/client/src/utils/storage.js b/client/src/utils/storage.js index bb7fdce7..2e7a84fb 100644 --- a/client/src/utils/storage.js +++ b/client/src/utils/storage.js @@ -1,66 +1,214 @@ -// TODO: general methods to access/retrieve from storage, to be generalized -// https://developer.mozilla.org/fr/docs/Web/API/API_IndexedDB -// https://dexie.org/ +import { extractTime } from "@/utils/timeControl"; + +// TODO: show game structure +//const newItem = [ +// { gameId: "", players: [], timeControl: "", clocks: [] } +//]; + +function dbOperation(callback) +{ + let db = null; + let DBOpenRequest = window.indexedDB.open("vchess", 4); + + DBOpenRequest.onerror = function(event) { + alert("Database error: " + event.target.errorCode); + }; + + DBOpenRequest.onsuccess = function(event) { + db = DBOpenRequest.result; + callback(db); + db.close(); + }; + + DBOpenRequest.onupgradeneeded = function(event) { + let db = event.target.result; + db.onerror = function(event) { + alert("Error while loading database: " + event.target.errorCode); + }; + // Create objectStore for vchess->games + db.createObjectStore("games", { keyPath: "gameId" }); + } +} + +// Optional callback to get error status +function addGame(game, callback) +{ + dbOperation((db) => { + let transaction = db.transaction(["games"], "readwrite"); + if (callback) + { + transaction.oncomplete = function() { + callback({}); //everything's fine + } + transaction.onerror = function() { + callback({errmsg: "addGame failed: " + transaction.error}); + }; + } + let objectStore = transaction.objectStore("games"); + objectStore.add(game); + }); +} + +// Clear current live game from localStorage +function clear() { + localStorage.removeItem("gameInfo"); + localStorage.removeItem("gameState"); +} + +// Current live game: +function getCurrent() +{ + return Object.assign({}, + JSON.parse(localStorage.getItem("gameInfo")), + JSON.parse(localStorage.getItem("gameState"))); +} + +// Only called internally after a score update +function transferToDb() +{ + addGame(getCurrent(), (err) => { + if (!!err.errmsg) + return err; + clear(); + }); +} export const GameStorage = { - init: function(myid, oppid, gameId, variant, mycolor, fenStart) + // localStorage: + init: function(o) { - localStorage.setItem("myid", myid); - localStorage.setItem("gameId", gameId); - localStorage.setItem("vname", variant); - localStorage.setItem("mycolor", mycolor); - localStorage.setItem("fenStart", fenStart); - localStorage.setItem("moves", []); + // Extract times (in [milli]seconds), set clocks, store in localStorage + const tc = extractTime(o.timeControl); + + // game infos: constant + const gameInfo = + { + gameId: o.gameId, + vname: o.vname, + fenStart: o.fenStart, + players: o.players, + timeControl: o.timeControl, + increment: tc.increment, + mode: "live", //function for live games only + }; + + // game state: will be updated + const gameState = + { + fen: o.fenStart, + moves: [], + clocks: [...Array(o.players.length)].fill(tc.mainTime), + initime: (o.initime ? Date.now() : undefined), + score: "*", + }; + + localStorage.setItem("gameInfo", JSON.stringify(gameInfo)); + localStorage.setItem("gameState", JSON.stringify(gameState)); + }, + + getInitime: function() + { + const gameState = JSON.parse(localStorage.getItem("gameState")); + return gameState.initime; }, + // localStorage: // TODO: also option to takeback a move ? - update: function(move) + // NOTE: for live games only (all on server for corr) + update: function(o) //colorIdx, move, fen, addTime, initime, score { - let moves = JSON.parse(localStorage.getItem("moves")); - moves.push(move); - localStorage.setItem("moves", JSON.stringify(moves)); + let gameState = JSON.parse(localStorage.getItem("gameState")); + if (!!o.move) + { + gameState.moves.push(o.move); + gameState.fen = o.fen; + if (!!o.addTime) //NaN if first move in game + gameState.clocks[o.colorIdx] += o.addTime; + } + if (!!o.initime) //just a flag (true) + gameState.initime = Date.now(); + if (!!o.score) + gameState.score = o.score; + localStorage.setItem("gameState", JSON.stringify(gameState)); + if (!!o.score && o.score != "*") + transferToDb(); //game is over }, - // "computer mode" clearing is done through the menu - clear: function() + // indexedDB: + // Since DB requests are asynchronous, require a callback using the result + // TODO: option for remote retrieval (third arg, or just "gameRef") + getLocal: function(gameId, callback) { - // TODO: refresh, and implement "transfert" function (to indexedDB) - delete localStorage["myid"]; - delete localStorage["oppid"]; - delete localStorage["gameId"]; - delete localStorage["variant"]; - delete localStorage["mycolor"]; - delete localStorage["fenStart"]; - delete localStorage["moves"]; + dbOperation((db) => { + let objectStore = db.transaction('games').objectStore('games'); + if (!gameId) //retrieve all + { + let games = []; + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + // if there is still another cursor to go, keep running this code + if (cursor) + { + games.push(cursor.value); + cursor.continue(); + } + else + callback(games); + } + } + else //just one game + { + objectStore.get(gameId).onsuccess = function(event) { + callback(event.target.result); + } + } + }); }, - // TODO: game or gameInfo ?! - get: function(gameRef) + // Delete a game in indexedDB + remove: function(gameId, callback) + { + dbOperation((db) => { + let transaction = db.transaction(["games"], "readwrite"); + if (callback) + { + transaction.oncomplete = function() { + callback({}); //everything's fine + } + transaction.onerror = function() { + callback({errmsg: "game removal failed: " + transaction.error}); + }; + } + transaction.objectStore("games").delete(gameId); + }); + }, + + // Retrieve any live game from its identifiers (remote or not, running or not) + // NOTE: need callback because result might be obtained asynchronously + get: function(gameRef, callback) { const gid = gameRef.id; const rid = gameRef.rid; //may be blank - let game = {}; - if (localStorage.getItem("gameId") === gid) + if (!!rid) { - // Retrieve running game from localStorage - game.score = localStorage.getItem("score"); - game.mycolor = localStorage.getItem("mycolor"); - game.fenStart = localStorage.getItem("fenStart"); - game.fen = localStorage.getItem("fen"); - game.moves = JSON.parse(localStorage.getItem("moves")); - game.players = JSON.parse(localStorage.getItem("players")); - game.started = JSON.parse(localStorage.getItem("started")); - game.clocks = JSON.parse(localStorage.getItem("clocks")); - game.timeControl = localStorage.getItem("timeControl"); - game.increment = localStorage.getItem("increment"); - game.vname = localStorage.getItem("vname"); - game.mode = "live"; + // TODO: send request to server which forward to user sid == rid, + // need to listen to "remote game" event in main hall ? + return callback({}); //means "the game will arrive later" (TODO...) } - else + + const gameInfoStr = localStorage.getItem("gameInfo"); + if (gameInfoStr) { - // Find the game in indexedDB, on server or remotely: TODO + const gameInfo = JSON.parse(gameInfoStr); + if (gameInfo.gameId == gid) + { + const gameState = JSON.parse(localStorage.getItem("gameState")); + return callback(Object.assign({}, gameInfo, gameState)); + } } - return game; + + // Game is local and not running + GameStorage.getLocal(gid, callback); }, };