From 8c3820e7d229c5635b651708341a4a909c6366ab Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 11 Nov 2024 09:52:25 -0800 Subject: [PATCH] prevent workspace from re-importing projects on db reset (#10261) * prevent workspace from re-importing projects on db reset * handle clients from before this fix --- webapp/src/idbworkspace.ts | 73 +++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/webapp/src/idbworkspace.ts b/webapp/src/idbworkspace.ts index 3841887bedd..b6abc8b59d2 100644 --- a/webapp/src/idbworkspace.ts +++ b/webapp/src/idbworkspace.ts @@ -204,36 +204,57 @@ async function migrateOldIndexedDbAsync() { await pxt.BrowserUtils.clearTutorialInfoDbAsync(); }); + const previousDbPrefix = "legacy"; + const currentDbPrefix = getCurrentDBPrefix() || "default"; + try { await legacyDb.openAsync(); const currentDb = await getCurrentDbAsync(); - await copyTableEntriesAsync(legacyDb, currentDb, HEADERS_TABLE, true); - await copyTableEntriesAsync(legacyDb, currentDb, TEXTS_TABLE, true); + await copyTableEntriesAsync(legacyDb, currentDb, HEADERS_TABLE, true, previousDbPrefix, currentDbPrefix); + await copyTableEntriesAsync(legacyDb, currentDb, TEXTS_TABLE, true, previousDbPrefix, currentDbPrefix); } catch (e) { pxt.reportException(e); } } async function migratePrefixesAsync() { - if (!getCurrentDBPrefix()) return; + const currentDbPrefix = getCurrentDBPrefix(); + if (!currentDbPrefix) return; const currentVersion = pxt.semver.parse(pxt.appTarget.versions.target); const currentMajor = currentVersion.major; const previousMajor = currentMajor - 1; const previousDbPrefix = previousMajor < 0 ? "" : pxt.appTarget.appTheme.browserDbPrefixes[previousMajor]; const currentDb = await getCurrentDbAsync(); + const migrationDb = await getMigrationDbAsync(); + const dummyEntryKey = migrationDbPrefixUpgradeKey(previousDbPrefix, currentDbPrefix, "dummy"); // If headers are already in the new db, migration must have already happened - if ((await currentDb.getAllAsync(HEADERS_TABLE)).length) return; - - const prevDb = await getDbAsync(previousDbPrefix); + if ((await currentDb.getAllAsync(HEADERS_TABLE)).length) { + // Check to see if we've populated the migration db. This only applies to older clients + // from before we started tracking prefix upgrades in the migration db. Populating this db + // should be a one-time operation and is necessary to ensure that reset works correctly + // in browsers that loaded the page sometime before the migration fix was released. + if (await migrationDb.getAsync(HEADERS_TABLE, dummyEntryKey)) return; + + const prevDb = await getDbAsync(previousDbPrefix); + await populatePrefixMigrationDb(prevDb, currentDb, HEADERS_TABLE, previousDbPrefix, currentDbPrefix); + await populatePrefixMigrationDb(prevDb, currentDb, TEXTS_TABLE, previousDbPrefix, currentDbPrefix); + } + else { + // Copy everything over to the current db + const prevDb = await getDbAsync(previousDbPrefix); + + await copyTableEntriesAsync(prevDb, currentDb, HEADERS_TABLE, false, previousDbPrefix, currentDbPrefix); + await copyTableEntriesAsync(prevDb, currentDb, TEXTS_TABLE, false, previousDbPrefix, currentDbPrefix); + await copyTableEntriesAsync(prevDb, currentDb, SCRIPT_TABLE, false, previousDbPrefix, currentDbPrefix); + await copyTableEntriesAsync(prevDb, currentDb, HOSTCACHE_TABLE, false, previousDbPrefix, currentDbPrefix); + await copyTableEntriesAsync(prevDb, currentDb, GITHUB_TABLE, false, previousDbPrefix, currentDbPrefix); + } - await copyTableEntriesAsync(prevDb, currentDb, HEADERS_TABLE, false); - await copyTableEntriesAsync(prevDb, currentDb, TEXTS_TABLE, false); - await copyTableEntriesAsync(prevDb, currentDb, SCRIPT_TABLE, false); - await copyTableEntriesAsync(prevDb, currentDb, HOSTCACHE_TABLE, false); - await copyTableEntriesAsync(prevDb, currentDb, GITHUB_TABLE, false); + // Stick a dummy marker in the migration db to indicate that we did migrate everything + await migrationDb.setAsync(HEADERS_TABLE, { id: dummyEntryKey }); } let _dbPromises: pxt.Map> = {}; @@ -269,12 +290,36 @@ async function getDbAsync(prefix = "__default") { } } -async function copyTableEntriesAsync(fromDb: pxt.BrowserUtils.IDBWrapper, toDb: pxt.BrowserUtils.IDBWrapper, storeName: string, dontOverwrite: boolean) { +async function copyTableEntriesAsync(fromDb: pxt.BrowserUtils.IDBWrapper, toDb: pxt.BrowserUtils.IDBWrapper, storeName: string, dontOverwrite: boolean, fromPrefix: string, toPrefix: string) { + const migrationDb = await getMigrationDbAsync(); + for (const entry of await fromDb.getAllAsync(storeName)) { + const key = migrationDbPrefixUpgradeKey(fromPrefix, toPrefix, entry.id); + if (await migrationDb.getAsync(storeName, key)) continue; + const existing = dontOverwrite && !!(await toDb.getAsync(storeName, entry.id)); if (!existing) { await toDb.setAsync(storeName, entry); + await migrationDb.setAsync(storeName, { + id: key + }); + } + } +} + +async function populatePrefixMigrationDb(fromDb: pxt.BrowserUtils.IDBWrapper, toDb: pxt.BrowserUtils.IDBWrapper, storeName: string, fromPrefix: string, toPrefix: string) { + const migrationDb = await getMigrationDbAsync(); + + for (const entry of await fromDb.getAllAsync(storeName)) { + const key = migrationDbPrefixUpgradeKey(fromPrefix, toPrefix, entry.id); + if (await migrationDb.getAsync(storeName, key)) continue; + + // If this header id was actually migrated, add an entry for it in the migration db + if (await toDb.getAsync(storeName, entry.id)) { + await migrationDb.setAsync(storeName, { + id: key + }); } } } @@ -719,6 +764,10 @@ function migrationDbKey(prefix: string, id: string) { return `${prefix}--${id}`; }; +function migrationDbPrefixUpgradeKey(oldPrefix: string, newPrefix: string, id: string) { + return `${oldPrefix}--${newPrefix}--${id}`; +} + export const provider: WorkspaceProvider = { getAsync, setAsync,