Browse Source

Remove users from backend, leave only sessions. Simplify storage

infinite
A.Olokhtonov 2 years ago
parent
commit
dec07b4edc
  1. 3
      server/config.js
  2. 5
      server/enums.js
  3. 8
      server/http.js
  4. 119
      server/recv.js
  5. 41
      server/send.js
  6. 10
      server/server.js
  7. 156
      server/storage.js

3
server/config.js

@ -2,4 +2,5 @@ export const HOST = '127.0.0.1';
export const PORT = 3003; export const PORT = 3003;
export const DATADIR = 'data'; export const DATADIR = 'data';
export const SYNC_TIMEOUT = 1000; export const SYNC_TIMEOUT = 1000;
export const IMAGEDIR = 'images'; export const IMAGEDIR = 'images';
export const DEBUG_PRINT = true;

5
server/enums.js

@ -23,9 +23,4 @@ export const MESSAGE = Object.freeze({
FULL: 103, FULL: 103,
FIRE: 104, FIRE: 104,
JOIN: 105, JOIN: 105,
});
export const SNS = Object.freeze({
DESK: 1,
SESSION: 2,
}); });

8
server/http.js

@ -7,13 +7,11 @@ export async function route(req) {
if (url.pathname === '/api/image') { if (url.pathname === '/api/image') {
const desk_id = url.searchParams.get('deskId') || '0'; const desk_id = url.searchParams.get('deskId') || '0';
const formData = await req.formData(); const formdata = await req.formData();
const file = formData.get('file'); const file = formdata.get('file');
const image_id = math.fast_random32(); const image_id = math.fast_random32();
await Bun.write(config.IMAGEDIR + '/' + image_id, file); Bun.write(config.IMAGEDIR + '/' + image_id, file);
storage.put_image(image_id, desk_id);
return new Response(image_id); return new Response(image_id);
} else if (url.pathname === '/api/ping') { } else if (url.pathname === '/api/ping') {

119
server/recv.js

@ -2,9 +2,10 @@ import * as des from './deserializer';
import * as send from './send'; import * as send from './send';
import * as math from './math'; import * as math from './math';
import * as storage from './storage'; import * as storage from './storage';
import * as config from './config';
import { SESSION, MESSAGE, EVENT } from './enums'; import { SESSION, MESSAGE, EVENT } from './enums';
import { sessions, desks } from './storage'; import { sessions, desks, queries } from './storage';
// Session ACKed events up to SN // Session ACKed events up to SN
function recv_ack(d, session) { function recv_ack(d, session) {
@ -13,45 +14,20 @@ function recv_ack(d, session) {
session.state = SESSION.READY; session.state = SESSION.READY;
session.sn = sn; session.sn = sn;
console.log(`ack ${sn} in`); if (config.DEBUG_PRINT) console.log(`ack ${sn} in`);
}
function handle_event(session, event) {
switch (event.type) {
case EVENT.STROKE: {
event.stroke_id = math.fast_random32();
storage.put_stroke(event.stroke_id, session.desk_id, event.points, event.width, event.color);
storage.put_event(event);
break;
}
case EVENT.ERASER:
case EVENT.IMAGE:
case EVENT.IMAGE_MOVE:
case EVENT.UNDO: {
storage.put_event(event);
break;
}
default: {
console.error('fuck');
console.trace();
process.exit(1);
}
}
} }
async function recv_syn(d, session) { async function recv_syn(d, session) {
const lsn = des.u32(d); const lsn = des.u32(d);
const count = des.u32(d); const count = des.u32(d);
console.log(`syn ${lsn} in, total size = ${d.size}`); if (config.DEBUG_PRINT) console.log(`syn ${lsn} in, total size = ${d.size}`);
const we_expect = lsn - session.lsn; const we_expect = lsn - session.lsn;
const first = count - we_expect; const first = count - we_expect;
const events = []; const events = [];
console.log(`we expect ${we_expect}, count ${count}`); if (config.DEBUG_PRINT) console.log(`we expect ${we_expect}, count ${count}`);
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
const event = des.event(d); const event = des.event(d);
@ -67,8 +43,15 @@ async function recv_syn(d, session) {
desks[session.desk_id].events.push(...events); desks[session.desk_id].events.push(...events);
session.lsn = lsn; session.lsn = lsn;
storage.save_desk_sn(session.desk_id, desks[session.desk_id].sn); storage.queries.update_desk_sn.run({
storage.save_session_lsn(session.id, lsn); '$id': session.desk_id,
'$sn': desks[session.desk_id].sn
});
storage.queries.update_session_lsn.run({
'$id': session.id,
'$lsn': lsn
});
send.send_ack(session.ws, lsn); send.send_ack(session.ws, lsn);
send.sync_desk(session.desk_id); send.sync_desk(session.desk_id);
@ -79,6 +62,30 @@ function recv_fire(d, session) {
event.user_id = session.user_id; event.user_id = session.user_id;
switch (event.type) {
case EVENT.SET_COLOR: {
session.color = event.color;
storage.queries.update_session_color.run({
'$id': session.id,
'$color': event.color
});
break;
}
case EVENT.SET_WIDTH: {
session.width = event.width;
storage.queries.update_session_width.run({
'$id': session.id,
'width': event.width
});
break;
}
}
for (const sid in sessions) { for (const sid in sessions) {
const other = sessions[sid]; const other = sessions[sid];
@ -98,6 +105,56 @@ function recv_fire(d, session) {
} }
} }
function handle_event(session, event) {
switch (event.type) {
case EVENT.STROKE: {
event.stroke_id = math.fast_random32();
storage.queries.insert_stroke.run({
'$id': event.stroke_id,
'$width': event.width,
'$color': event.color,
'$points': event.points
});
storage.queries.insert_event.run({
'$type': event.type,
'$desk_id': session.desk_id,
'$session_id': session.id,
'$stroke_id': event.stroke_id,
'$image_id': 0,
'$x': 0,
'$y': 0,
});
break;
}
case EVENT.ERASER:
case EVENT.IMAGE:
case EVENT.IMAGE_MOVE:
case EVENT.UNDO: {
storage.queries.insert_event.run({
'$type': event.type,
'$desk_id': session.desk_id,
'$session_id': session.id,
'$stroke_id': event.stroke_id || 0,
'$image_id': event.image_id || 0,
'$x': event.x || 0,
'$y': event.y || 0,
});
break;
}
default: {
console.error('fuck');
console.trace();
process.exit(1);
}
}
}
export async function handle_message(ws, d) { export async function handle_message(ws, d) {
if (!(ws.data.session_id in sessions)) { if (!(ws.data.session_id in sessions)) {
return; return;

41
server/send.js

@ -71,10 +71,14 @@ function create_session(ws, desk_id) {
sn: 0, sn: 0,
lsn: 0, lsn: 0,
ws: ws, ws: ws,
color: 0x00,
width: 8,
}; };
storage.create_user(user); storage.queries.insert_session.run({
storage.create_session(session); '$id': session.id,
'$desk_id': desk_id
});
sessions[session.id] = session; sessions[session.id] = session;
@ -91,7 +95,7 @@ export async function send_init(ws) {
const desk = desks[desk_id]; const desk = desks[desk_id];
let opcode = MESSAGE.INIT; let opcode = MESSAGE.INIT;
let size = 1 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count let size = 1 + 4 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count + user count
let session = null; let session = null;
if (session_id in sessions && sessions[session_id].desk_id == desk_id) { if (session_id in sessions && sessions[session_id].desk_id == desk_id) {
@ -107,8 +111,20 @@ export async function send_init(ws) {
session.ws = ws; session.ws = ws;
session.sn = 0; // Always re-send everything on reconnect session.sn = 0; // Always re-send everything on reconnect
session.state = SESSION.OPENED; session.state = SESSION.OPENED;
session.color = 0x00;
session.width = 8;
console.log(`session ${session.id} opened`); if (config.DEBUG_PRINT) console.log(`session ${session.id} opened`);
let user_count = 0;
for (const sid in sessions) {
const other_session = sessions[sid];
if (other_session.id !== session_id && other_session.desk_id === desk_id) {
++user_count;
size += 4 + 4 + 2; // user id + color + width
}
}
for (const event of desk.events) { for (const event of desk.events) {
size += event_size(event); size += event_size(event);
@ -125,6 +141,17 @@ export async function send_init(ws) {
} }
ser.u32(s, desk.events.length); ser.u32(s, desk.events.length);
ser.u32(s, user_count);
for (const sid in sessions) {
const other_session = sessions[sid];
if (other_session.id !== session_id && other_session.desk_id === desk_id) {
// console.log(other_session.id, other_session.color, other_session.width);
ser.u32(s, other_session.id);
ser.u32(s, other_session.color);
ser.u16(s, other_session.width);
}
}
for (const event of desk.events) { for (const event of desk.events) {
ser.event(s, event); ser.event(s, event);
@ -144,7 +171,7 @@ export function send_ack(ws, lsn) {
ser.u8(s, MESSAGE.ACK); ser.u8(s, MESSAGE.ACK);
ser.u32(s, lsn); ser.u32(s, lsn);
console.log(`ack ${lsn} out`); if (config.DEBUG_PRINT) console.log(`ack ${lsn} out`);
ws.send(s.buffer); ws.send(s.buffer);
} }
@ -182,7 +209,7 @@ async function sync_session(session_id) {
let count = desk.sn - session.sn; let count = desk.sn - session.sn;
if (count === 0) { if (count === 0) {
console.log('client ACKed all events'); if (config.DEBUG_PRINT) console.log('client ACKed all events');
return; return;
} }
@ -202,7 +229,7 @@ async function sync_session(session_id) {
ser.event(s, event); ser.event(s, event);
} }
console.debug(`syn ${desk.sn} out`); if (config.DEBUG_PRINT) console.log(`syn ${desk.sn} out`);
await session.ws.send(s.buffer); await session.ws.send(s.buffer);

10
server/server.js

@ -1,13 +1,11 @@
import * as config from './config'; import * as config from './config';
import * as storage from './storage'; import * as storage from './storage';
import * as http_server from './http'; import * as http_server from './http';
import * as math from './math';
import * as ser from './serializer';
import * as des from './deserializer'; import * as des from './deserializer';
import * as send from './send'; import * as send from './send';
import * as recv from './recv'; import * as recv from './recv';
import { MESSAGE, EVENT, SESSION, SNS } from './enums'; import { MESSAGE, EVENT, SESSION } from './enums';
import { sessions, desks } from './storage'; import { sessions, desks } from './storage';
export function startup() { export function startup() {
@ -29,7 +27,11 @@ export function startup() {
events: [], events: [],
}; };
storage.create_desk(desk_id); storage.queries.insert_desk.run({
'$id': desk_id,
'$title': `Desk ${desk_id}`
});
desks[desk_id] = desk; desks[desk_id] = desk;
} }

156
server/storage.js

@ -3,11 +3,12 @@ import * as sqlite from 'bun:sqlite';
import { EVENT, SESSION } from './enums'; import { EVENT, SESSION } from './enums';
// In-memory views
export const sessions = {}; export const sessions = {};
export const desks = {}; export const desks = {};
export const queries = {};
let db = null; let db = null;
const queries = {};
export function startup() { export function startup() {
const path = `${config.DATADIR}/db.sqlite`; const path = `${config.DATADIR}/db.sqlite`;
@ -20,31 +21,12 @@ export function startup() {
title TEXT title TEXT
);`).run(); );`).run();
db.query(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
login TEXT
);`).run();
db.query(`CREATE TABLE IF NOT EXISTS sessions ( db.query(`CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
user_id INTEGER,
desk_id INTEGER, desk_id INTEGER,
lsn INTEGER, lsn INTEGER,
color INTEGER,
FOREIGN KEY (user_id) width INTEGER,
REFERENCES users (id)
ON DELETE CASCADE
ON UPDATE NO ACTION,
FOREIGN KEY (desk_id)
REFERENCES desks (id)
ON DELETE CASCADE
ON UPDATE NO ACTION
);`).run();
db.query(`CREATE TABLE IF NOT EXISTS images (
id INTEGER PRIMARY KEY,
desk_id INTEGER,
FOREIGN KEY (desk_id) FOREIGN KEY (desk_id)
REFERENCES desks (id) REFERENCES desks (id)
@ -54,22 +36,16 @@ export function startup() {
db.query(`CREATE TABLE IF NOT EXISTS strokes ( db.query(`CREATE TABLE IF NOT EXISTS strokes (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
desk_id INTEGER,
points BLOB,
width INTEGER, width INTEGER,
color INTEGER, color INTEGER,
points BLOB
FOREIGN KEY (desk_id)
REFERENCES desks (id)
ON DELETE CASCADE
ON UPDATE NO ACTION
);`).run(); );`).run();
db.query(`CREATE TABLE IF NOT EXISTS events ( db.query(`CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
type INTEGER, type INTEGER,
desk_id INTEGER, desk_id INTEGER,
user_id INTEGER, session_id INTEGER,
stroke_id INTEGER, stroke_id INTEGER,
image_id INTEGER, image_id INTEGER,
x INTEGER, x INTEGER,
@ -80,71 +56,52 @@ export function startup() {
ON DELETE CASCADE ON DELETE CASCADE
ON UPDATE NO ACTION ON UPDATE NO ACTION
FOREIGN KEY (user_id) FOREIGN KEY (session_id)
REFERENCES users (id) REFERENCES sessions (id)
ON DELETE CASCADE ON DELETE NO ACTION
ON UPDATE NO ACTION ON UPDATE NO ACTION
FOREIGN KEY (stroke_id) FOREIGN KEY (stroke_id)
REFERENCES strokes (id) REFERENCES strokes (id)
ON DELETE CASCADE ON DELETE CASCADE
ON UPDATE NO ACTION ON UPDATE NO ACTION
FOREIGN KEY (image_id)
REFERENCES images (id)
ON DELETE CASCADE
ON UPDATE NO ACTION
);`).run(); );`).run();
db.query(`CREATE INDEX IF NOT EXISTS idx_events_desk_id // INSERT
ON events (desk_id); queries.insert_desk = db.query('INSERT INTO desks (id, title, sn) VALUES ($id, $title, 0)');
`).run(); queries.insert_stroke = db.query('INSERT INTO strokes (id, width, color, points) VALUES ($id, $width, $color, $points)');
queries.insert_session = db.query('INSERT INTO sessions (id, desk_id, lsn) VALUES ($id, $desk_id, 0)');
queries.insert_event = db.query('INSERT INTO events (type, desk_id, session_id, stroke_id, image_id, x, y) VALUES ($type, $desk_id, $session_id, $stroke_id, $image_id, $x, $y)');
db.query(`CREATE INDEX IF NOT EXISTS idx_strokes_desk_id // UPDATE
ON strokes (desk_id); queries.update_desk_sn = db.query('UPDATE desks SET sn = $sn WHERE id = $id');
`).run(); queries.update_session_lsn = db.query('UPDATE sessions SET lsn = $lsn WHERE id = $id');
queries.update_session_color = db.query('UPDATE sessions SET color = $color WHERE id = $id');
queries.update_session_width = db.query('UPDATE sessions SET width = $width WHERE id = $id');
const res1 = db.query('SELECT COUNT(id) as count FROM desks').get(); const res1 = db.query('SELECT COUNT(id) as count FROM desks').get();
const res2 = db.query('SELECT COUNT(id) as count FROM events').get(); const res2 = db.query('SELECT COUNT(id) as count FROM events').get();
const res3 = db.query('SELECT COUNT(id) as count FROM strokes').get(); const res3 = db.query('SELECT COUNT(id) as count FROM strokes').get();
const res4 = db.query('SELECT COUNT(id) as count FROM users').get(); const res4 = db.query('SELECT COUNT(id) as count FROM sessions').get();
const res5 = db.query('SELECT COUNT(id) as count FROM sessions').get();
const res6 = db.query('SELECT COUNT(id) as count FROM images').get();
queries.desks = db.query('SELECT id, sn FROM desks');
queries.events = db.query('SELECT * FROM events');
queries.sessions = db.query('SELECT id, lsn, user_id, desk_id FROM sessions');
queries.strokes = db.query('SELECT * FROM strokes');
queries.empty_desk = db.query('INSERT INTO desks (id, title, sn) VALUES (?1, ?2, 0)');
queries.desk_strokes = db.query('SELECT id, points FROM strokes WHERE desk_id = ?1');
queries.put_desk_stroke = db.query('INSERT INTO strokes (id, desk_id, points, width, color) VALUES (?1, ?2, ?3, ?4, ?5)');
queries.clear_desk_events = db.query('DELETE FROM events WHERE desk_id = ?1');
queries.set_desk_sn = db.query('UPDATE desks SET sn = ?1 WHERE id = ?2');
queries.save_session_lsn = db.query('UPDATE sessions SET lsn = ?1 WHERE id = ?2');
queries.create_session = db.query('INSERT INTO sessions (id, lsn, user_id, desk_id) VALUES (?1, 0, ?2, ?3)');
queries.create_user = db.query('INSERT INTO users (id, login) VALUES (?1, ?2)');
queries.put_event = db.query('INSERT INTO events (type, desk_id, user_id, stroke_id, image_id, x, y) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)');
queries.put_image = db.query('INSERT INTO images (id, desk_id) VALUES (?1, ?2)');
console.log(`Storing data in ${path}`); console.log(`Storing data in ${path}`);
console.log(`Entity count at startup: console.log(`Entity count at startup:
${res1.count} desks ${res1.count} desks
${res2.count} events ${res2.count} events
${res3.count} strokes ${res3.count} strokes
${res4.count} users ${res4.count} sessions
${res5.count} sessions `);
${res6.count} images`
);
const stored_desks = get_desks(); // Init in-memory view: merge strokes into events, set all sessions to closed
const stored_events = get_events(); const stored_desks = db.query('SELECT * FROM desks').all();
const stored_strokes = get_strokes(); const stored_events = db.query('SELECT * FROM events').all();
const stored_sessions = get_sessions(); const stored_strokes = db.query('SELECT * FROM strokes').all();
const stored_sessions = db.query('SELECT * FROM sessions').all();
const stroke_dict = {}; const stroke_dict = {};
for (const stroke of stored_strokes) { for (const stroke of stored_strokes) {
stroke.points = new Uint16Array(stroke.points.buffer); stroke.points = new Float32Array(stroke.points.buffer);
stroke_dict[stroke.id] = stroke; stroke_dict[stroke.id] = stroke;
} }
@ -161,7 +118,6 @@ export function startup() {
event.width = stroke.width; event.width = stroke.width;
} }
desks[event.desk_id].events.push(event); desks[event.desk_id].events.push(event);
} }
@ -170,60 +126,4 @@ export function startup() {
session.ws = null; session.ws = null;
sessions[session.id] = session; sessions[session.id] = session;
} }
}
export function get_strokes() {
return queries.strokes.all();
}
export function get_sessions() {
return queries.sessions.all();
}
export function get_desks() {
return queries.desks.all();
}
export function get_events() {
return queries.events.all();
}
export function get_desk_strokes(desk_id) {
return queries.desk_strokes.all(desk_id);
}
export function put_event(event) {
return queries.put_event.get(event.type, event.desk_id || 0, event.user_id || 0, event.stroke_id || 0, event.image_id || 0, event.x || 0, event.y || 0);
}
export function put_stroke(stroke_id, desk_id, points, width, color) {
return queries.put_desk_stroke.get(stroke_id, desk_id, new Uint8Array(points.buffer, points.byteOffset, points.byteLength), width, color);
}
export function clear_events(desk_id) {
return queries.clear_desk_events.get(desk_id);
}
export function create_desk(desk_id, title = 'untitled') {
return queries.empty_desk.get(desk_id, title);
}
export function save_desk_sn(desk_id, sn) {
return queries.set_desk_sn.get(sn, desk_id);
}
export function create_session(session) {
return queries.create_session.get(session.id, session.user_id, session.desk_id);
}
export function create_user(user) {
return queries.create_user.get(user.id, user.login);
}
export function save_session_lsn(session_id, lsn) {
return queries.save_session_lsn.get(lsn, session_id);
}
export function put_image(image_id, desk_id) {
return queries.put_image.get(image_id, desk_id);
} }
Loading…
Cancel
Save