ctucx.git: trainsearch

web based trip-planner, fork of https://cyberchaos.dev/yuka/trainsearch

commit f86b6adbec619415f237f1c8135c81e10e80edb6
parent 755b0014c96df6414bd77ffde9137773f31036fb
Author: Yureka <yuka@yuka.dev>
Date: Mon, 5 Sep 2022 17:35:11 +0200

datastorage abstraction
4 files changed, 138 insertions(+), 69 deletions(-)
M
src/app_functions.js
|
38
++++++++++++++++----------------------
M
src/dataStorage.js
|
155
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
M
src/searchView.js
|
7
++++---
M
src/settings.js
|
7
++-----
diff --git a/src/app_functions.js b/src/app_functions.js
@@ -13,9 +13,6 @@ subscribeSettings(async () => {
 	if (settings.showRIL100Names) await loadDS100();
 });
 
-export const getJourneysHistory = () => db.getAll('journeysHistory');
-
-export const addHistoryEntry = newEntry => db.put('journeysHistory', newEntry);
 
 const addJourneys = async data => {
 	if (!data) return false;

@@ -30,22 +27,19 @@ const addJourneys = async data => {
 		journeyId: ''
 	};
 
-	const tx = db.transaction(['journey', 'journeysOverview', 'journeysHistory'], 'readwrite');
-	const journeyStore = tx.objectStore('journey');
-	const journeysOverviewStore = tx.objectStore('journeysOverview');
-	const journeysHistoryStore = tx.objectStore('journeysHistory');
-	let proms = data.journeys.map(j => {
-		j.settings = data.settings;
-		j.slug = data.slug;
-		journeyStore.put(j);
+	const journeyEntries = data.journeys.map(j => {
+		return {
+			...j,
+			settings: data.settings,
+			slug: data.slug,
+		};
 	});
-	proms.push(journeysOverviewStore.put({
+	const journeysOverviewEntry = {
 		...data,
 		journeys: data.journeys.map(j => j.refreshToken),
-	}));
-	proms.push(journeysHistoryStore.put(historyEntry));
-	await Promise.all(proms);
-	await tx.done;
+	};
+
+	await db.addJourneys(journeyEntries, journeysOverviewEntry, historyEntry);
 
 	//const lastHistoryEntry = (await getJourneysHistory()).slice(-1);
 	//if (lastHistoryEntry[0]

@@ -89,13 +83,13 @@ const processJourney = journey => {
 };
 
 export const getJourneys = async slug => {
-	let data = await db.get('journeysOverview', slug);
+	let data = await db.getJourneysOverview(slug);
 	data.journeys = await Promise.all(data.journeys.map(x => getJourney(x, data.profile)));
 	return data;
 };
 
 export const getJourney = async (refreshToken, profile) => {
-	let data = await db.get('journey', refreshToken);
+	let data = await db.getJourney(refreshToken);
 	const settings = mkSettings();
 	if (!data || JSON.stringify(data.settings) != JSON.stringify(settings)) {
 		data = await refreshJourney(refreshToken, profile);

@@ -105,7 +99,7 @@ export const getJourney = async (refreshToken, profile) => {
 };
 
 export const getMoreJourneys = async (slug, mode) => {
-	const saved = await db.get('journeysOverview', slug);
+	const saved = await db.getJourneysOverview(slug);
 	const params = { ...saved.params, ...mkSettings() };
 	params[mode+'Than'] = saved[mode+'Ref'];
 	let { departure, arrival, from, to, ...moreOpt } = params;

@@ -135,20 +129,20 @@ export const getMoreJourneys = async (slug, mode) => {
 	await addJourneys(res);
 };
 export const refreshJourneys = async (slug) => {
-	const saved = await db.get('journeysOverview', slug);
+	const saved = await db.getJourneysOverview(slug);
 	await Promise.all(saved.journeys.map(x => refreshJourney(x, saved.profile || "db")));
 };
 export const refreshJourney = async (refreshToken, profile) => {
 	const client = await getHafasClient(profile || settings.profile || "db");
 	const requestSettings = mkSettings();
 	const [saved, data] = await Promise.all([
-		db.get('journey', refreshToken),
+		db.getJourney(refreshToken),
 		client.refreshJourney(trainsearchToHafas(refreshToken), requestSettings)
 	]);
 	data.settings = requestSettings;
 	data.refreshToken = hafasToTrainsearch(data.refreshToken);
 	if (saved) data.slug = saved.slug;
-	db.put('journey', data);
+	db.updateJourney(data);
 	return data;
 };
 
diff --git a/src/dataStorage.js b/src/dataStorage.js
@@ -5,46 +5,123 @@ const dbName = devMode ? 'trainsearch_dev' : 'trainsearch';
 
 export let db;
 
+class LocalStorage {
+	async addJourneys(journeyEntries, overviewEntry, historyEntry) {
+	}
+	async getJourneysHistory() {
+	}
+	async addHistoryEntry(newEntry) {
+	}
+	async getSettings() {
+	}
+	async modifySettings(callback) {
+	}
+	async getJourneysOverview(slug) {
+	}
+	async getJourney(refreshToken) {
+	}
+	async updateJourney(data) {
+	}
+}
+
+class IDBStorage {
+	constructor(idb) {
+		this.idb = idb;
+	}
+
+	static async open() {
+		const idb = await openDB(dbName, 2, {
+			upgrade: (db, oldVersion, newVersion, transaction) => {
+				console.log(`upgrading database from ${oldVersion} to ${newVersion}`);
+				switch (oldVersion) {
+				case 0:
+					/*
+					 * in database scheme v1, the `journeys` object store stores whole searches, including journey data
+					 */
+					db.createObjectStore('journeys', {keyPath: 'slug'});
+					db.createObjectStore('journeysHistory', {autoIncrement: true});
+					db.createObjectStore('settings');
+				case 1:
+					/*
+					 * in database scheme v2, there are the following data stores:
+					 * `journey` for storing individual journeys with their refreshToken as index
+					 * `journeysOverview`t contains the search results with the `.journeys` field being an array of refreshTokens.
+					 *
+					 * in comparison to v1, there are these additional changes:
+					 * `journeys` object store is deleted.
+					 * `journeysHistory` object store is cleared/recreated.
+					 */
+
+					/* clean up old stuff */
+					db.deleteObjectStore('journeys');
+					db.deleteObjectStore('journeysHistory');
+
+					/* create new stores */
+					db.createObjectStore('journey', {keyPath: 'refreshToken'});
+					db.createObjectStore('journeysOverview', {keyPath: 'slug'});
+					db.createObjectStore('journeysHistory', {autoIncrement: true});
+				case 2:
+					//... add migrations for 2->3 here
+				}
+			},
+			blocking: async () => {
+				db.close();
+				location.reload();
+			},
+		});
+		return new IDBStorage(idb);
+	}
+
+	async addJourneys(journeyEntries, overviewEntry, historyEntry) {
+		const tx = this.idb.transaction(['journey', 'journeysOverview', 'journeysHistory'], 'readwrite');
+		const journeyStore = tx.objectStore('journey');
+		const journeysOverviewStore = tx.objectStore('journeysOverview');
+		const journeysHistoryStore = tx.objectStore('journeysHistory');
+		let proms = journeyEntries.map(j => {
+			journeyStore.put(j);
+		});
+		proms.push(journeysOverviewStore.put(overviewEntry));
+		proms.push(journeysHistoryStore.put(historyEntry));
+		proms.push(tx.done);
+		await Promise.all(proms);
+	}
+	async getJourneysHistory() {
+		return await this.idb.getAll('journeysHistory');
+	}
+	async addHistoryEntry(newEntry) {
+		await this.idb.put('journeysHistory', newEntry);
+	}
+	async getSettings() {
+		return await this.idb.get('settings', 'settings');
+	}
+	async modifySettings(cb) {
+		const tx = this.idb.transaction('settings', 'readwrite');
+		const newSettings = callback(JSON.parse(JSON.stringify(this.getSettings())));
+		await Promise.all([
+			tx.store.put(newSettings, 'settings'),
+			tx.done
+		]);
+		return newSettings;
+	}
+	async getJourneysOverview(slug) {
+		return await this.idb.get('journeysOverview', slug);
+	}
+	async getJourney(refreshToken) {
+		return await this.idb.get('journey', refreshToken);
+	}
+	async updateJourney(data) {
+		return await this.idb.put('journey', data);
+	}
+}
+
 export const initDataStorage = async () => {
-	db = await openDB(dbName, 2, {
-		upgrade: (db, oldVersion, newVersion, transaction) => {
-			console.log(`upgrading database from ${oldVersion} to ${newVersion}`);
-			switch (oldVersion) {
-			case 0:
-				/*
-				 * in database scheme v1, the `journeys` object store stores whole searches, including journey data
-				 */
-				db.createObjectStore('journeys', {keyPath: 'slug'});
-				db.createObjectStore('journeysHistory', {autoIncrement: true});
-				db.createObjectStore('settings');
-			case 1:
-				/*
-				 * in database scheme v2, there are the following data stores:
-				 * `journey` for storing individual journeys with their refreshToken as index
-				 * `journeysOverview`t contains the search results with the `.journeys` field being an array of refreshTokens.
-				 *
-				 * in comparison to v1, there are these additional changes:
-				 * `journeys` object store is deleted.
-				 * `journeysHistory` object store is cleared/recreated.
-				 */
-
-				/* clean up old stuff */
-				db.deleteObjectStore('journeys');
-				db.deleteObjectStore('journeysHistory');
-
-				/* create new stores */
-				db.createObjectStore('journey', {keyPath: 'refreshToken'});
-				db.createObjectStore('journeysOverview', {keyPath: 'slug'});
-				db.createObjectStore('journeysHistory', {autoIncrement: true});
-			case 2:
-				//... add migrations for 2->3 here
-			}
-		},
-		blocking: async () => {
-			db.close();
-			location.reload();
-		},
-	});
+	try {
+		db = await IDBStorage.open();
+	} catch(e) {
+		console.log("indexeddb initialization failed: ", e);
+		console.log("falling back to localstorage");
+		db = new LocalStorage();
+	}
 };
 
 export const clearDataStorage = () => deleteDB(dbName);
diff --git a/src/searchView.js b/src/searchView.js
@@ -1,5 +1,6 @@
 import { showDiv, ElementById, padZeros, isValidDate, formatFromTo } from './helpers.js';
-import { parseName, ConsoleLog, t, loadDS100, getJourneys, getJourneysHistory, newJourneys } from './app_functions.js';
+import { db } from './dataStorage.js';
+import { parseName, ConsoleLog, t, loadDS100, getJourneys, newJourneys } from './app_functions.js';
 import { modifySettings, settings } from './settings.js';
 import { go } from './router.js';
 import { html, render } from 'lit-html';

@@ -155,7 +156,7 @@ const journeysHistoryAction = (journeysHistory, element) => {
 };
 
 export const searchView = async () => {
-	const journeysHistory = (await getJourneysHistory()).slice().reverse();	
+	const journeysHistory = (await db.getJourneysHistory()).slice().reverse();	
 	render(searchTemplate(journeysHistory), ElementById('content'));
 
 	ElementById('from').focus();

@@ -331,7 +332,7 @@ export const swapFromTo = () => {
 };
 
 export const setFromHistory = async id => {
-	const entry = (await getJourneysHistory())[id];
+	const entry = (await db.getJourneysHistory())[id];
 	if (!entry) return;
 	setSuggestion(entry.fromPoint, 'from');
 	setSuggestion(entry.toPoint, 'to');
diff --git a/src/settings.js b/src/settings.js
@@ -41,15 +41,12 @@ export const subscribeSettings = cb => {
 };
 
 export const initSettings = async () => {
-	settings = (await db.get('settings', 'settings')) || defaultSettings;
+	settings = (await db.getSettings) || defaultSettings;
 	for (const cb of subscribers) await cb();
 };
 
 export const modifySettings = async callback => {
-	const tx = db.transaction('settings', 'readwrite');
-	const newSettings = callback(JSON.parse(JSON.stringify(settings)));
-	await tx.store.put(newSettings, 'settings');
-	await tx.done;
+	const newSettings = db.modifySettings(callback);
 	Object.freeze(newSettings);
 	settings = newSettings;
 	for (const cb of subscribers) await cb();