diff --git a/mikupad.html b/mikupad.html
index e6c3680..c65ffd3 100644
--- a/mikupad.html
+++ b/mikupad.html
@@ -4934,7 +4934,7 @@ class IndexedDBAdapter {
const cursor = event.target.result;
if (cursor) {
if (cursor.key !== 'nextSessionId' && cursor.key !== 'selectedSessionId') {
- allTables[cursor.key] = cursor.value.name;
+ allTables[cursor.key] = cursor.value;
}
cursor.continue();
} else {
@@ -5286,13 +5286,13 @@ class SessionStorage extends AbstractStorage {
return data;
}
- async deleteFromDatabase(db, key) {
- await super.deleteFromDatabase(db, key);
- await this.nameStorage.deleteFromDatabase(db, key);
- }
+ async deleteFromDatabase(db, key) {
+ await super.deleteFromDatabase(db, key);
+ await this.nameStorage.deleteFromDatabase(db, key);
+ }
async saveSessionToDB(sessionId) {
- const sessionData = this.sessions[sessionId];
+ const { name, ...sessionData } = this.sessions[sessionId];
if (!sessionData || sessionData.inactive)
return;
const db = await this.openDatabase();
diff --git a/server/server.js b/server/server.js
index bb98569..70a5e10 100644
--- a/server/server.js
+++ b/server/server.js
@@ -6,6 +6,7 @@ const path = require('path');
const minimist = require('minimist');
const axios = require('axios');
const open = require('open');
+const zlib = require('zlib');
const app = express();
@@ -56,56 +57,97 @@ app.use((req, res, next) => {
res.status(401).send('Authentication required.');
});
+const compressData = (data) => {
+ return new Promise((resolve, reject) => {
+ zlib.gzip(data, (err, buffer) => {
+ if (err) return reject(err);
+ resolve(buffer);
+ });
+ });
+};
+
+const decompressData = (buffer) => {
+ return new Promise((resolve, reject) => {
+ zlib.gunzip(buffer, (err, decompressed) => {
+ if (err) return reject(err);
+ resolve(decompressed.toString());
+ });
+ });
+};
+
const runMigrationToV3 = (db) => {
return new Promise((resolve, reject) => {
- // Check if the 'names' table exists to determine if a 2>3 migration is needed.
- db.get("SELECT name FROM sqlite_master WHERE type='table' AND name='names'", (err, row) => {
+ // Check if the 'sessions' table exists and the 'names' table doesn't to determine if a 2->3 migration is needed.
+ const migrationCheckSql = `
+ SELECT 'migration_needed' as status
+ FROM sqlite_master
+ WHERE type = 'table' AND name = 'sessions'
+ AND NOT EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'names');
+ `;
+
+ db.get(migrationCheckSql, (err, row) => {
if (err) {
- reject(err);
- return;
+ return reject(err);
}
- if (!row) {
- const migrationScript = `
- BEGIN TRANSACTION;
+ if (row) {
+ // This is a V2 database. We need to extract names and compress data.
+ db.serialize(async () => {
+ try {
+ const migrateTable = async (tableName, processRow) => {
+ await new Promise((res, rej) => db.run(`ALTER TABLE ${tableName} RENAME TO ${tableName}_old`, (err) => err ? rej(err) : res()));
+ await new Promise((res, rej) => db.run(`CREATE TABLE ${tableName} (key TEXT PRIMARY KEY, data BLOB)`, (err) => err ? rej(err) : res()));
+ const rows = await new Promise((res, rej) => db.all(`SELECT key, data FROM ${tableName}_old`, [], (err, rows) => err ? rej(err) : res(rows)));
+ for (const row of rows) {
+ await processRow(row);
+ }
+ await new Promise((res, rej) => db.run(`DROP TABLE ${tableName}_old`, (err) => err ? rej(err) : res()));
+ };
- CREATE TABLE names (
- key TEXT PRIMARY KEY,
- data TEXT
- );
+ db.run("BEGIN TRANSACTION;");
+
+ await new Promise((res, rej) => db.run(`CREATE TABLE names (key TEXT PRIMARY KEY, data TEXT);`, (err) => err ? rej(err) : res()));
- INSERT INTO names (key, data)
- SELECT
- sessions.key,
- json_extract(sessions.data, '$.name')
- FROM
- sessions
- WHERE
- json_extract(sessions.data, '$.name') IS NOT NULL;
+ await migrateTable('sessions', async (row) => {
+ const sessionData = JSON.parse(row.data);
+ const sessionName = sessionData.name;
- UPDATE sessions
- SET data = json_remove(data, '$.name')
- WHERE
- json_extract(data, '$.name') IS NOT NULL;
+ if (sessionName) {
+ await new Promise((res, rej) => db.run("INSERT INTO names (key, data) VALUES (?, ?)", [row.key, sessionName], (err) => err ? rej(err) : res()));
+ delete sessionData.name;
+ }
- COMMIT;
- `;
+ const compressedData = await compressData(JSON.stringify(sessionData));
+ await new Promise((res, rej) => db.run("INSERT INTO sessions (key, data) VALUES (?, ?)", [row.key, compressedData], (err) => err ? rej(err) : res()));
+ });
- db.exec(migrationScript, (err) => {
- if (err) {
- return reject(err);
+ await migrateTable('templates', async (row) => {
+ const compressedData = await compressData(row.data);
+ await new Promise((res, rej) => db.run("INSERT INTO templates (key, data) VALUES (?, ?)", [row.key, compressedData], (err) => err ? rej(err) : res()));
+ });
+
+ db.run("COMMIT;", (err) => {
+ if (err) {
+ return reject(err);
+ }
+ // Migration was successful!
+ resolve(true);
+ });
+
+ } catch (e) {
+ db.run("ROLLBACK;");
+ reject(e);
}
- // Migration success!
- resolve(true);
});
} else {
- // This is already a V3 db.
+ // This is already a V3 db, no migration needed.
resolve(false);
}
});
});
};
+
// Open a database connection
const db = new sqlite3.Database('./web-session-storage.db', (err) => {
if (err) {
@@ -114,16 +156,16 @@ const db = new sqlite3.Database('./web-session-storage.db', (err) => {
}
db.serialize(() => {
- db.run(`CREATE TABLE IF NOT EXISTS sessions (key TEXT PRIMARY KEY, data TEXT)`);
- db.run(`CREATE TABLE IF NOT EXISTS templates (key TEXT PRIMARY KEY, data TEXT)`);
+ db.run(`CREATE TABLE IF NOT EXISTS sessions (key TEXT PRIMARY KEY, data BLOB)`);
+ db.run(`CREATE TABLE IF NOT EXISTS templates (key TEXT PRIMARY KEY, data BLOB)`);
db.run(`CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)`);
- runMigrationToV3(db).then((res) => {
- if (!res) {
+ runMigrationToV3(db).then((didMigrate) => {
+ if (!didMigrate) {
+ // If no migration happened, it's either a V3 DB or a entirely new DB, ensure names table exists.
db.run(`CREATE TABLE IF NOT EXISTS names (key TEXT PRIMARY KEY, data TEXT)`);
}
- // No need to check the result for now, but it would be necessary when V4 is introduced.
db.run(`INSERT OR REPLACE INTO meta (key, value) VALUES ('version', 3)`);
}).catch((err) => {
console.error("Migration failed:", err.message);
@@ -283,36 +325,62 @@ app.post('/load', (req, res) => {
if (!normStoreName) {
return res.status(400).json({ ok: false, message: 'Invalid store name provided' });
}
- db.get(`SELECT data FROM ${normStoreName} WHERE key = ?`, [key], (err, row) => {
+ db.get(`SELECT data FROM ${normStoreName} WHERE key = ?`, [key], async (err, row) => {
if (err) {
- res.status(500).json({ ok: false, message: 'Error querying the database' });
- } else if (row) {
- res.json({ ok: true, result: normStoreName === "names" ? row.data : JSON.parse(row.data) });
- } else {
- res.status(404).json({ ok: false, message: 'Key not found' });
+ return res.status(500).json({ ok: false, message: 'Error querying the database' });
+ }
+ if (!row) {
+ return res.status(404).json({ ok: false, message: 'Key not found' });
+ }
+
+ try {
+ if (normStoreName !== "names") {
+ const decompressed = await decompressData(row.data);
+ res.json({ ok: true, result: JSON.parse(decompressed) });
+ } else {
+ res.json({ ok: true, result: row.data });
+ }
+ } catch (e) {
+ res.status(500).json({ ok: false, message: 'Failed to decompress or parse data.' });
}
});
});
// POST route to save data
-app.post('/save', (req, res) => {
+app.post('/save', async (req, res) => {
const { storeName, key, data } = req.body;
const normStoreName = normalizeStoreName(storeName);
if (!normStoreName) {
return res.status(400).json({ ok: false, message: 'Invalid store name provided' });
}
- db.run(`INSERT OR REPLACE INTO ${normStoreName} (key, data) VALUES (?, ?)`, [key, normStoreName === "names" ? data : JSON.stringify(data)], (err) => {
- if (err) {
- res.status(500).json({ ok: false, message: 'Error writing to the database' });
+
+ try {
+ let dataToStore;
+ if (normStoreName !== "names") {
+ dataToStore = await compressData(JSON.stringify(data));
} else {
- res.json({ ok: true, result: 'Data saved successfully' });
+ dataToStore = data;
}
- });
+
+ db.run(`INSERT OR REPLACE INTO ${normStoreName} (key, data) VALUES (?, ?)`, [key, dataToStore], (err) => {
+ if (err) {
+ res.status(500).json({ ok: false, message: 'Error writing to the database' });
+ } else {
+ res.json({ ok: true, result: 'Data saved successfully' });
+ }
+ });
+ } catch (e) {
+ res.status(500).json({ ok: false, message: 'Failed to compress data.' });
+ }
});
// POST route to update session name
app.post('/rename', (req, res) => {
const { storeName, key, newName } = req.body;
+ const normStoreName = normalizeStoreName(storeName);
+ if (normStoreName !== 'sessions') {
+ return res.status(400).json({ ok: false, message: 'Renaming is only supported for sessions' });
+ }
db.run(
`
UPDATE names
@@ -337,15 +405,26 @@ app.post('/all', (req, res) => {
if (!normStoreName) {
return res.status(400).json({ ok: false, message: 'Invalid store name provided' });
}
- db.all(`SELECT key, data FROM ${normStoreName}`, [], (err, rows) => {
+ db.all(`SELECT key, data FROM ${normStoreName}`, [], async (err, rows) => {
if (err) {
- res.status(500).json({ ok: false, message: 'Error querying the database' });
- } else {
+ return res.status(500).json({ ok: false, message: 'Error querying the database' });
+ }
+
+ try {
const all = {};
- rows.forEach((row) => {
- all[row.key] = normStoreName === "names" ? row.data : JSON.parse(row.data);
- });
+ if (normStoreName !== "names") {
+ await Promise.all(rows.map(async (row) => {
+ const decompressed = await decompressData(row.data);
+ all[row.key] = JSON.parse(decompressed);
+ }));
+ } else {
+ rows.forEach((row) => {
+ all[row.key] = row.data;
+ });
+ }
res.json({ ok: true, result: all });
+ } catch (e) {
+ res.status(500).json({ ok: false, message: 'Failed to decompress or parse data for one or more items.' });
}
});
});
@@ -380,12 +459,22 @@ app.post('/delete', (req, res) => {
if (!normStoreName) {
return res.status(400).json({ ok: false, message: 'Invalid store name provided' });
}
- db.run(`DELETE FROM ${normStoreName} WHERE key = ?`, [key], (err) => {
- if (err) {
- res.status(500).json({ ok: false, message: 'Error deleting from the database' });
- } else {
- res.json({ ok: true, result: 'Session deleted successfully' });
+ db.serialize(() => {
+ db.run("BEGIN TRANSACTION");
+
+ db.run(`DELETE FROM ${normStoreName} WHERE key = ?`, [key]);
+
+ if (normStoreName === 'sessions') {
+ db.run(`DELETE FROM names WHERE key = ?`, [key]);
}
+
+ db.run("COMMIT", (err) => {
+ if (err) {
+ db.run("ROLLBACK");
+ return res.status(500).json({ ok: false, message: 'Error deleting from the database' });
+ }
+ res.json({ ok: true, result: 'Session deleted successfully' });
+ });
});
});