X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Futils%2FgameStorage.js;h=f69de2e2629a64ac9afc7f9a31eb7f2f3b25ff5e;hb=1ef65040168ab7d55ce921abc9d63644a937d689;hp=75696a09630b2b9eabfe7285fe15781f438583ce;hpb=809ba2aa7682693e33f7b92c1fdfbb3b004befb4;p=vchess.git diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js index 75696a09..f69de2e2 100644 --- a/client/src/utils/gameStorage.js +++ b/client/src/utils/gameStorage.js @@ -1,13 +1,12 @@ // Game object: { // // Static informations: -// gameId: string +// id: string // vname: string, // fenStart: string, // players: array of sid+id+name, -// timeControl: string, +// cadence: string, // increment: integer (seconds), -// mode: string ("live" or "corr") -// imported: boolean (optional, default false) +// type: string ("live" or "corr") // // Game (dynamic) state: // fen: string, // moves: array of Move objects, @@ -16,120 +15,171 @@ // score: string (several options; '*' == running), // } -function dbOperation(callback) -{ +import { store } from "@/store"; + +function dbOperation(callback) { let db = null; - let DBOpenRequest = window.indexedDB.open("vchess", 4); + let DBOpenRequest = window.indexedDB.open("vchess", 5); DBOpenRequest.onerror = function(event) { - alert("Database error: " + event.target.errorCode); + alert(store.state.tr[ + "Database error: stop private browsing, or update your browser"]); + callback("error", null); }; - DBOpenRequest.onsuccess = function(event) { + DBOpenRequest.onsuccess = function() { db = DBOpenRequest.result; - callback(db); + callback(null, 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" }); - } + let upgradeTransaction = event.target.transaction; + let objectStore = undefined; + if (!db.objectStoreNames.contains("games")) + objectStore = db.createObjectStore("games", { keyPath: "id" }); + else + objectStore = upgradeTransaction.objectStore("games"); + if (!objectStore.indexNames.contains("score")) + // To sarch games by score (useful for running games) + objectStore.createIndex("score", "score", { unique: false }); + if (!objectStore.indexNames.contains("created")) + // To search by date intervals. Two games cannot start at the same time + objectStore.createIndex("created", "created", { unique: true }); + }; } -export const GameStorage = -{ +export const GameStorage = { // Optional callback to get error status - add: function(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}); - }; + add: function(game, callback) { + dbOperation((err, db) => { + if (!!err) { + callback("error"); + return; } - let objectStore = transaction.objectStore("games"); - objectStore.add(game); + let transaction = db.transaction("games", "readwrite"); + transaction.oncomplete = function() { + // Everything's fine + callback(); + }; + transaction.onerror = function(err) { + // Duplicate key error (most likely) + callback(err); + }; + transaction.objectStore("games").add(game); }); }, - // TODO: also option to takeback a move ? - // NOTE: for live games only (all on server for corr) - update: function(gameId, obj) //colorIdx, nextIdx, move, fen, addTime, score - { - dbOperation((db) => { - let objectStore = db.transaction("games", "readwrite").objectStore("games"); + // obj: chat, move, fen, clocks, score[Msg], initime, ... + update: function(gameId, obj) { + // live + dbOperation((err, db) => { + let objectStore = db + .transaction("games", "readwrite") + .objectStore("games"); objectStore.get(gameId).onsuccess = function(event) { - const game = event.target.result; - if (!!obj.move) - { - game.moves.push(obj.move); - game.fen = obj.fen; - if (!!obj.addTime) //NaN if first move in game - game.clocks[obj.colorIdx] += obj.addTime; - game.initime[obj.nextIdx] = Date.now(); + // Ignoring error silently: shouldn't happen now. TODO? + if (event.target.result) { + let game = event.target.result; + // Hidden tabs are delayed, to prevent multi-updates: + if (obj.moveIdx < game.moves.length) return; + Object.keys(obj).forEach(k => { + if (k == "move") game.moves.push(obj[k]); + else if (k == "chat") game.chats.push(obj[k]); + else if (k == "chatRead") game.chatRead = Date.now(); + else if (k == "delchat") game.chats = []; + else game[k] = obj[k]; + }); + objectStore.put(game); //save updated data } - if (!!obj.score) - game.score = obj.score; - objectStore.put(game); //save updated data - } + }; }); }, - // Retrieve any live game from its identifiers (locally, running or not) - // NOTE: need callback because result is obtained asynchronously - get: function(gameId, callback) - { - 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); + // Retrieve (all) running local games + getRunning: function(callback) { + dbOperation((err, db) => { + let objectStore = db + .transaction("games", "readonly") + .objectStore("games"); + let index = objectStore.index("score"); + const range = IDBKeyRange.only("*"); + let games = []; + index.openCursor(range).onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) callback(games); + else { + // If there is still another cursor to go, keep running this code + let g = cursor.value; + // Do not retrieve moves or clocks (unused in list mode) + g.movesCount = g.moves.length; + delete g.moves; + delete g.clocks; + delete g.initime; + games.push(g); + cursor.continue(); } - } - else //just one game - { - objectStore.get(gameId).onsuccess = function(event) { - callback(event.target.result); + }; + }); + }, + + // Retrieve completed local games + getNext: function(upperDt, callback) { + dbOperation((err, db) => { + let objectStore = db + .transaction("games", "readonly") + .objectStore("games"); + let index = objectStore.index("created"); + const range = IDBKeyRange.upperBound(upperDt); + let games = []; + index.openCursor(range).onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + // Most recent games first: + games = games.sort((g1, g2) => g2.created - g1.created); + // TODO: 20 games showed per request is arbitrary + callback(games.slice(0, 20)); } - } + else { + // If there is still another cursor to go, keep running this code + let g = cursor.value; + if (g.score != "*") { + // Do not retrieve moves or clocks (unused in list mode) + g.movesCount = g.moves.length; + delete g.moves; + delete g.clocks; + delete g.initime; + games.push(g); + } + cursor.continue(); + } + }; + }); + }, + + // Retrieve any game from its identifier. + // NOTE: need callback because result is obtained asynchronously + get: function(gameId, callback) { + dbOperation((err, db) => { + let objectStore = db.transaction("games").objectStore("games"); + objectStore.get(gameId).onsuccess = function(event) { + // event.target.result is null if game not found + callback(event.target.result); + }; }); }, // Delete a game in indexedDB - remove: function(gameId, callback) - { - dbOperation((db) => { - let transaction = db.transaction(["games"], "readwrite"); - if (callback) - { + remove: function(gameId, callback) { + dbOperation((err, db) => { + if (!err) { + let transaction = db.transaction("games", "readwrite"); transaction.oncomplete = function() { - callback({}); //everything's fine - } - transaction.onerror = function() { - callback({errmsg: "removeGame failed: " + transaction.error}); + callback(); //everything's fine }; + transaction.objectStore("games").delete(gameId); } - transaction.objectStore("games").delete(gameId); }); - }, + } };