Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid unwanted project persistence #1009

Merged
merged 1 commit into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 6 additions & 15 deletions src/fs/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,15 @@ import {
import { fromByteArray, toByteArray } from "base64-js";
import EventEmitter from "events";
import sortBy from "lodash.sortby";
import { lineNumFromUint8Array } from "../common/text-util";
import { BoardId } from "../device/board-id";
import { FlashDataSource, HexGenerationError } from "../device/device";
import { Logging } from "../logging/logging";
import { Host } from "./host";
import { MicroPythonSource } from "../micropython/micropython";
import { asciiToBytes, generateId } from "./fs-util";
import {
FSStorage,
InMemoryFSStorage,
SessionStorageFSStorage,
SplitStrategyStorage,
} from "./storage";
import { Host } from "./host";
import { PythonProject } from "./initial-project";
import { lineNumFromUint8Array } from "../common/text-util";
import { MicroPythonSource } from "../micropython/micropython";
import { FSStorage } from "./storage";

const commonFsSize = 20 * 1024;

Expand Down Expand Up @@ -168,11 +163,7 @@ export class FileSystem extends EventEmitter implements FlashDataSource {
private microPythonSource: MicroPythonSource
) {
super();
this.storage = new SplitStrategyStorage(
new InMemoryFSStorage(undefined),
SessionStorageFSStorage.create(),
logging
);
this.storage = host.createStorage(logging);
this.project = {
files: [],
id: generateId(),
Expand Down Expand Up @@ -216,7 +207,7 @@ export class FileSystem extends EventEmitter implements FlashDataSource {
if (!this.initializing) {
this.initializing = (async () => {
this._dirty = await this.storage.isDirty();
if (!(await this.exists(MAIN_FILE))) {
if (await this.host.shouldReinitializeProject(this.storage)) {
// Do this ASAP to unblock the editor.
this.cachedInitialProject = await this.host.createInitialProject();
if (this.cachedInitialProject.projectName) {
Expand Down
40 changes: 38 additions & 2 deletions src/fs/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import {
projectFilesToBase64,
} from "./initial-project";
import { parseMigrationFromUrl } from "./migration";
import {
FSStorage,
InMemoryFSStorage,
SessionStorageFSStorage,
SplitStrategyStorage,
} from "./storage";

const messages = {
type: "pyeditor",
Expand All @@ -30,22 +36,45 @@ const messages = {
};

export interface Host {
createStorage(logging: Logging): FSStorage;
shouldReinitializeProject(storage: FSStorage): Promise<boolean>;
createInitialProject(): Promise<PythonProject>;
notifyReady(fs: FileSystem): void;
}

export class DefaultHost implements Host {
constructor(private url: string = "") {}

async createInitialProject(): Promise<PythonProject> {
createStorage(logging: Logging): FSStorage {
return new SplitStrategyStorage(
new InMemoryFSStorage(undefined),
SessionStorageFSStorage.create(),
logging
);
}

async shouldReinitializeProject(storage: FSStorage): Promise<boolean> {
const migration = parseMigrationFromUrl(this.url);
if (migration) {
return {
return true;
}
return !(await storage.exists(MAIN_FILE));
}

async createInitialProject(): Promise<PythonProject> {
const migrationParseResult = parseMigrationFromUrl(this.url);
if (migrationParseResult) {
const { migration, postMigrationUrl } = migrationParseResult;
const project = {
files: projectFilesToBase64({
[MAIN_FILE]: migration.source,
}),
projectName: migration.meta.name,
};
// Remove the migration information from the URL so that a refresh
// will reload from storage not remigrate.
window.history.replaceState(null, "", postMigrationUrl);
return project;
}
return defaultInitialProject;
}
Expand All @@ -58,6 +87,13 @@ export class IframeHost implements Host {
private window: Window,
private debounceDelay: number = 1_000
) {}
createStorage(logging: Logging): FSStorage {
return new InMemoryFSStorage(undefined);
}
async shouldReinitializeProject(storage: FSStorage): Promise<boolean> {
// If there is persistence then it is the embedder's problem.
return true;
}
createInitialProject(): Promise<PythonProject> {
return new Promise((resolve) => {
this.window.addEventListener("load", () =>
Expand Down
18 changes: 11 additions & 7 deletions src/fs/migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,23 @@ import { isMigration, parseMigrationFromUrl } from "./migration";

// The heart project.
export const testMigrationUrl =
"http://localhost:3000/#import:#project:XQAAgACRAAAAAAAAAAA9iImmlGSt1R++5LD+ZJ36cRz46B+lhYtNRoWF0nijpaVyZlK7ACfSpeoQpgfk21st4ty06R4PEOM4sSAXBT95G3en+tghrYmE+YJp6EiYgzA9ThKkyShWq2UdvmCzqxoNfYc1wlmTqlNv/Piaz3WoSe3flvr/ItyLl0aolQlEpv4LA8A=";
// origin needs to match jest's testUrl
"http://localhost/#import:#project:XQAAgACRAAAAAAAAAAA9iImmlGSt1R++5LD+ZJ36cRz46B+lhYtNRoWF0nijpaVyZlK7ACfSpeoQpgfk21st4ty06R4PEOM4sSAXBT95G3en+tghrYmE+YJp6EiYgzA9ThKkyShWq2UdvmCzqxoNfYc1wlmTqlNv/Piaz3WoSe3flvr/ItyLl0aolQlEpv4LA8A=";

describe("parseMigrationFromUrl", () => {
it("parses valid URL", () => {
const migration = parseMigrationFromUrl(testMigrationUrl);
expect(migration).toEqual({
meta: {
cloudId: "microbit.org",
comment: "",
editor: "python",
name: "Hearts",
migration: {
meta: {
cloudId: "microbit.org",
comment: "",
editor: "python",
name: "Hearts",
},
source: `from microbit import *\r\ndisplay.show(Image.HEART)`,
},
source: `from microbit import *\r\ndisplay.show(Image.HEART)`,
postMigrationUrl: "http://localhost/",
});
});
it("undefined for nonsense", () => {
Expand Down
17 changes: 14 additions & 3 deletions src/fs/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,25 @@ export const isMigration = (v: any): v is Migration =>
typeof v.meta?.name === "string" &&
typeof v.source === "string";

export const parseMigrationFromUrl = (url: string): Migration | undefined => {
const urlPart = url.split("#project:")[1];
interface MigrationParseResult {
migration: Migration;
postMigrationUrl: string;
}

export const parseMigrationFromUrl = (
url: string
): MigrationParseResult | undefined => {
const parts = url.split("#project:");
const urlPart = parts[1];
try {
if (urlPart) {
const bytes = toByteArray(urlPart);
const json = JSON.parse(LZMA.decompress(bytes));
if (isMigration(json)) {
return json;
let postMigrationUrl = parts[0];
// This was previously stripped off by the versioner but for now do it ourselves:
postMigrationUrl = postMigrationUrl.replace(/#import:$/, "");
return { migration: json, postMigrationUrl };
}
}
} catch (e) {
Expand Down