Merge branch 'master' of github.com:yagu0/vchess
[vchess.git] / client / src / utils / gameStorage.js
1 // Game object: {
2 // // Static informations:
3 // id: string
4 // vname: string,
5 // fenStart: string,
6 // players: array of sid+id+name,
7 // cadence: string,
8 // increment: integer (seconds),
9 // type: string ("live" or "corr")
10 // // Game (dynamic) state:
11 // fen: string,
12 // moves: array of Move objects,
13 // clocks: array of integers,
14 // initime: array of integers (when clock start running),
15 // score: string (several options; '*' == running),
16 // }
17
18 import { store } from "@/store";
19
20 function dbOperation(callback) {
21 let db = null;
22 let DBOpenRequest = window.indexedDB.open("vchess", 4);
23
24 DBOpenRequest.onerror = function(event) {
25 alert(store.state.tr["Database error: stop private browsing, or update your browser"]);
26 callback("error", null);
27 };
28
29 DBOpenRequest.onsuccess = function() {
30 db = DBOpenRequest.result;
31 callback(null, db);
32 db.close();
33 };
34
35 DBOpenRequest.onupgradeneeded = function(event) {
36 let db = event.target.result;
37 let objectStore = db.createObjectStore("games", { keyPath: "id" });
38 // To sarch games by score (useful for running games)
39 objectStore.createIndex("score", "score", { unique: false });
40 // To search by date intervals. Two games cannot start at the same time
41 objectStore.createIndex("created", "created", { unique: true });
42 };
43 }
44
45 export const GameStorage = {
46 // Optional callback to get error status
47 add: function(game, callback) {
48 dbOperation((err,db) => {
49 if (!!err) {
50 callback("error");
51 return;
52 }
53 let transaction = db.transaction("games", "readwrite");
54 transaction.oncomplete = function() {
55 // Everything's fine
56 callback();
57 };
58 transaction.onerror = function(err) {
59 // Duplicate key error (most likely)
60 callback(err);
61 };
62 transaction.objectStore("games").add(game);
63 });
64 },
65
66 // obj: chat, move, fen, clocks, score[Msg], initime, ...
67 update: function(gameId, obj) {
68 // live
69 dbOperation((err,db) => {
70 let objectStore = db
71 .transaction("games", "readwrite")
72 .objectStore("games");
73 objectStore.get(gameId).onsuccess = function(event) {
74 // Ignoring error silently: shouldn't happen now. TODO?
75 if (event.target.result) {
76 let game = event.target.result;
77 // Hidden tabs are delayed, to prevent multi-updates:
78 if (obj.moveIdx < game.moves.length) return;
79 Object.keys(obj).forEach(k => {
80 if (k == "move") game.moves.push(obj[k]);
81 else game[k] = obj[k];
82 });
83 objectStore.put(game); //save updated data
84 }
85 };
86 });
87 },
88
89 // Retrieve (all) running local games
90 getRunning: function(callback) {
91 dbOperation((err,db) => {
92 let objectStore = db
93 .transaction("games", "readonly")
94 .objectStore("games");
95 let index = objectStore.index("score");
96 const range = IDBKeyRange.only("*");
97 let games = [];
98 index.openCursor(range).onsuccess = function(event) {
99 let cursor = event.target.result;
100 if (!cursor) callback(games);
101 else {
102 // If there is still another cursor to go, keep running this code
103 let g = cursor.value;
104 // Do not retrieve moves or clocks (unused in list mode)
105 g.movesCount = g.moves.length;
106 delete g.moves;
107 delete g.clocks;
108 delete g.initime;
109 games.push(g);
110 cursor.continue();
111 }
112 };
113 });
114 },
115
116 // Retrieve completed local games
117 getNext: function(upperDt, callback) {
118 dbOperation((err,db) => {
119 let objectStore = db
120 .transaction("games", "readonly")
121 .objectStore("games");
122 let index = objectStore.index("created");
123 const range = IDBKeyRange.upperBound(upperDt);
124 let games = [];
125 index.openCursor(range).onsuccess = function(event) {
126 let cursor = event.target.result;
127 if (!cursor) {
128 // Most recent games first:
129 games = games.sort((g1, g2) => g2.created - g1.created);
130 // TODO: 20 games showed per request is arbitrary
131 callback(games.slice(0, 20));
132 }
133 else {
134 // If there is still another cursor to go, keep running this code
135 let g = cursor.value;
136 if (g.score != "*") {
137 // Do not retrieve moves or clocks (unused in list mode)
138 g.movesCount = g.moves.length;
139 delete g.moves;
140 delete g.clocks;
141 delete g.initime;
142 games.push(g);
143 }
144 cursor.continue();
145 }
146 };
147 });
148 },
149
150 // Retrieve any game from its identifiers (locally or on server)
151 // NOTE: need callback because result is obtained asynchronously
152 get: function(gameId, callback) {
153 // Local game
154 dbOperation((err,db) => {
155 let objectStore = db.transaction("games").objectStore("games");
156 objectStore.get(gameId).onsuccess = function(event) {
157 // event.target.result is null if game not found
158 callback(event.target.result);
159 };
160 });
161 },
162
163 // Delete a game in indexedDB
164 remove: function(gameId, callback) {
165 dbOperation((err,db) => {
166 if (!err) {
167 let transaction = db.transaction("games", "readwrite");
168 transaction.oncomplete = function() {
169 callback(); //everything's fine
170 };
171 transaction.objectStore("games").delete(gameId);
172 }
173 });
174 }
175 };