mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-15 07:07:07 +00:00
batch of fixes
This commit is contained in:
1 parent
a67b982b1c
commit
eee76f05d4
9 files changed
+288
-66
No files matched your search
@@ -245,7 +245,7 @@ export async function createTauriBackend(): Promise<Backend> {
|
||||
|
||||
importsLegacyData: false,
|
||||
open_storage: () => invoke<StorageOpened>('open_storage'),
|
||||
flush_storage: () => invoke('flush_storage'),
|
||||
flush_storage: () => invoke('flush_storage').then(() => true),
|
||||
sync_storage: () => invoke('sync_storage'),
|
||||
set_setting: (key, value) => invoke('set_setting', { key, value }),
|
||||
delete_setting: (key) => invoke('delete_setting', { key }),
|
||||
|
||||
@@ -340,8 +340,8 @@ export interface Backend {
|
||||
onBehind: () => void,
|
||||
onNewer: () => void
|
||||
): Promise<StorageOpened>;
|
||||
/** Resolves when what was changed has been written to the storage. */
|
||||
flush_storage(): Promise<void>;
|
||||
/** Resolves when the writes of what was changed are done, to whether they went well. */
|
||||
flush_storage(): Promise<boolean>;
|
||||
/** Reads the files again if another window wrote them since, and tells what changed. */
|
||||
sync_storage(): Promise<Outcome>;
|
||||
set_setting(key: string, json: string): Promise<void>;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
import { LEGACY_IMPORTED_KEY, type LegacyData } from '$lib/storage/legacy-import';
|
||||
import { openStoredFiles, type StorageHost } from '$lib/engine/storage';
|
||||
|
||||
// a channel would keep the tests running: there are no other tabs here
|
||||
Object.defineProperty(globalThis, 'BroadcastChannel', { value: undefined, configurable: true });
|
||||
|
||||
const legacy: LegacyData = {
|
||||
files: [{ gpx: '<gpx/>', name: 'a' }],
|
||||
settings: { distanceUnits: '"imperial"', routing: 'false' },
|
||||
};
|
||||
|
||||
type Options = {
|
||||
stored?: Record<string, string>;
|
||||
storedFiles?: number;
|
||||
loads?: boolean;
|
||||
writes?: boolean;
|
||||
};
|
||||
|
||||
/** A host and a backend that note what is done, in order. */
|
||||
function setup({ stored = {}, storedFiles = 0, loads = true, writes = true }: Options = {}) {
|
||||
const calls: string[] = [];
|
||||
const settings: Record<string, string> = { ...stored };
|
||||
let files = storedFiles;
|
||||
const host = {
|
||||
backend: {
|
||||
importsLegacyData: true,
|
||||
open_storage: async () => ({ settings: { ...stored }, outcome: {} }),
|
||||
flush_storage: async () => {
|
||||
calls.push('flush');
|
||||
return writes;
|
||||
},
|
||||
set_setting: async (key: string, value: string) => {
|
||||
calls.push(key);
|
||||
settings[key] = value;
|
||||
},
|
||||
},
|
||||
visibility: new Map(),
|
||||
// like the engine, what was stored is only known once this is done
|
||||
sync: async () => {
|
||||
await Promise.resolve();
|
||||
calls.push('sync');
|
||||
},
|
||||
outdated() {},
|
||||
hasFiles: () => calls.includes('sync') && files > 0,
|
||||
loadFiles: async () => {
|
||||
calls.push('load');
|
||||
if (loads) {
|
||||
files += 1;
|
||||
}
|
||||
return loads;
|
||||
},
|
||||
deselect: async () => true,
|
||||
} as unknown as StorageHost;
|
||||
return { host, calls, settings };
|
||||
}
|
||||
|
||||
describe('importing the former database', () => {
|
||||
it('counts as done once the files are written', async () => {
|
||||
const { host, calls, settings } = setup();
|
||||
const opened = await openStoredFiles(host, async () => legacy);
|
||||
assert.deepEqual(calls, [
|
||||
'sync',
|
||||
'distanceUnits',
|
||||
'routing',
|
||||
'load',
|
||||
'flush',
|
||||
LEGACY_IMPORTED_KEY,
|
||||
]);
|
||||
assert.equal(settings[LEGACY_IMPORTED_KEY], 'true');
|
||||
assert.equal(opened.distanceUnits, '"imperial"');
|
||||
});
|
||||
|
||||
it('is tried again when the files cannot be loaded', async () => {
|
||||
const { host, settings } = setup({ loads: false });
|
||||
const opened = await openStoredFiles(host, async () => legacy);
|
||||
assert.equal(settings[LEGACY_IMPORTED_KEY], undefined);
|
||||
assert.equal(opened.distanceUnits, '"imperial"');
|
||||
});
|
||||
|
||||
it('is tried again when the files cannot be written', async () => {
|
||||
const { host, settings } = setup({ writes: false });
|
||||
await openStoredFiles(host, async () => legacy);
|
||||
assert.equal(settings[LEGACY_IMPORTED_KEY], undefined);
|
||||
});
|
||||
|
||||
it('is tried again when the database cannot be read', async () => {
|
||||
const { host, settings } = setup();
|
||||
await openStoredFiles(host, async () => {
|
||||
throw new Error('cannot read');
|
||||
});
|
||||
assert.equal(settings[LEGACY_IMPORTED_KEY], undefined);
|
||||
});
|
||||
|
||||
it('keeps the settings that were changed since a previous try', async () => {
|
||||
const { host, settings } = setup({ stored: { routing: 'true' } });
|
||||
const opened = await openStoredFiles(host, async () => legacy);
|
||||
assert.equal(settings.routing, 'true');
|
||||
assert.equal(opened.routing, 'true');
|
||||
assert.equal(settings.distanceUnits, '"imperial"');
|
||||
});
|
||||
|
||||
it('does not load the files twice: the stored ones mean it was done', async () => {
|
||||
const { host, calls, settings } = setup({ storedFiles: 3 });
|
||||
await openStoredFiles(host, async () => legacy);
|
||||
assert.ok(!calls.includes('load'));
|
||||
assert.equal(settings[LEGACY_IMPORTED_KEY], 'true');
|
||||
});
|
||||
|
||||
it('is done when there is nothing to import, and only once', async () => {
|
||||
const nothing = setup();
|
||||
await openStoredFiles(nothing.host, async () => undefined);
|
||||
assert.equal(nothing.settings[LEGACY_IMPORTED_KEY], 'true');
|
||||
|
||||
const done = setup({ stored: { [LEGACY_IMPORTED_KEY]: 'true' } });
|
||||
await openStoredFiles(done.host, async () => legacy);
|
||||
assert.ok(!done.calls.includes('load'));
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LEGACY_IMPORTED_KEY, readLegacyData } from '$lib/storage/legacy-import';
|
||||
import { LEGACY_IMPORTED_KEY, readLegacyData, type LegacyData } from '$lib/storage/legacy-import';
|
||||
import { parseVisibility, VISIBILITY_KEY, type Visibility } from '$lib/engine/file-visibility';
|
||||
import type { Outcome, StorageOpened } from 'gpx-wasm';
|
||||
import type { Backend } from './backend';
|
||||
@@ -15,7 +15,7 @@ export type StorageHost = {
|
||||
/** The explicit visibility per file, to fill with the one that was saved. */
|
||||
visibility: Map<string, Visibility>;
|
||||
/** Applies what a call changed to what the facade keeps of the files. */
|
||||
sync(update: Outcome): void;
|
||||
sync(update: Outcome): Promise<void>;
|
||||
/** Tells that what is stored comes from a newer version of the app: nothing can be saved. */
|
||||
outdated(): void;
|
||||
hasFiles(): boolean;
|
||||
@@ -66,7 +66,10 @@ function followOtherTabs(host: StorageHost): {
|
||||
*
|
||||
* The first time, what the previous versions of the app kept in the browser is imported.
|
||||
*/
|
||||
export async function openStoredFiles(host: StorageHost): Promise<Record<string, string>> {
|
||||
export async function openStoredFiles(
|
||||
host: StorageHost,
|
||||
readLegacy: () => Promise<LegacyData | undefined> = readLegacyData
|
||||
): Promise<Record<string, string>> {
|
||||
const { backend } = host;
|
||||
let opened: StorageOpened;
|
||||
const tabs = followOtherTabs(host);
|
||||
@@ -81,47 +84,68 @@ export async function openStoredFiles(host: StorageHost): Promise<Record<string,
|
||||
parseVisibility(settings[VISIBILITY_KEY]).forEach((visibility, fileId) =>
|
||||
host.visibility.set(fileId, visibility)
|
||||
);
|
||||
host.sync(opened.outcome);
|
||||
await host.sync(opened.outcome);
|
||||
|
||||
if (
|
||||
backend.importsLegacyData &&
|
||||
settings[LEGACY_IMPORTED_KEY] === undefined &&
|
||||
!host.hasFiles()
|
||||
) {
|
||||
settings = { ...settings, ...(await importLegacyData(host)) };
|
||||
await backend.flush_storage();
|
||||
if (backend.importsLegacyData && settings[LEGACY_IMPORTED_KEY] === undefined) {
|
||||
settings = { ...settings, ...(await importLegacyData(host, settings, readLegacy)) };
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
/** Puts the files and the settings of the former database in the engine. */
|
||||
async function importLegacyData(host: StorageHost): Promise<Record<string, string>> {
|
||||
/**
|
||||
* Puts the files and the settings of the former database in the engine. It only counts as done once
|
||||
* the files are written: until then, it is tried again the next time. The files that are stored
|
||||
* mean that it was done, by a visit that did not get to say so.
|
||||
*/
|
||||
async function importLegacyData(
|
||||
host: StorageHost,
|
||||
stored: Record<string, string>,
|
||||
readLegacy: () => Promise<LegacyData | undefined>
|
||||
): Promise<Record<string, string>> {
|
||||
const { backend } = host;
|
||||
const imported: Record<string, string> = {};
|
||||
try {
|
||||
const legacy = await readLegacyData();
|
||||
if (legacy) {
|
||||
const encoder = new TextEncoder();
|
||||
if (legacy.files.length > 0) {
|
||||
await host.loadFiles(
|
||||
legacy.files.map((file) => ({
|
||||
data: encoder.encode(file.gpx),
|
||||
name: file.name,
|
||||
}))
|
||||
);
|
||||
await host.deselect();
|
||||
}
|
||||
for (const [key, value] of Object.entries(legacy.settings)) {
|
||||
await backend.set_setting(key, value);
|
||||
imported[key] = value;
|
||||
if (!host.hasFiles()) {
|
||||
try {
|
||||
const legacy = await readLegacy();
|
||||
if (legacy) {
|
||||
// what was changed since a previous try is more recent
|
||||
for (const [key, value] of Object.entries(legacy.settings)) {
|
||||
if (stored[key] === undefined) {
|
||||
await backend.set_setting(key, value);
|
||||
imported[key] = value;
|
||||
}
|
||||
}
|
||||
if (legacy.files.length > 0 && !(await importFiles(host, legacy))) {
|
||||
return imported;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('The files of the previous version could not be imported', error);
|
||||
return imported;
|
||||
}
|
||||
} catch (error) {
|
||||
// it is tried again next time
|
||||
console.error('The files of the previous version could not be imported', error);
|
||||
return imported;
|
||||
}
|
||||
await backend.set_setting(LEGACY_IMPORTED_KEY, 'true');
|
||||
imported[LEGACY_IMPORTED_KEY] = 'true';
|
||||
return imported;
|
||||
}
|
||||
|
||||
/** Loads the files of the former database, and tells whether they are written. */
|
||||
async function importFiles(host: StorageHost, legacy: LegacyData): Promise<boolean> {
|
||||
const encoder = new TextEncoder();
|
||||
const loaded = await host.loadFiles(
|
||||
legacy.files.map((file) => ({
|
||||
data: encoder.encode(file.gpx),
|
||||
name: file.name,
|
||||
}))
|
||||
);
|
||||
if (!loaded) {
|
||||
console.error('The files of the previous version could not be loaded');
|
||||
return false;
|
||||
}
|
||||
await host.deselect();
|
||||
if (!(await host.backend.flush_storage())) {
|
||||
console.error('The files of the previous version could not be written');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in new issue
Block a user