diff --git a/src/storage/client_side_storage.ts b/src/storage/client_side_storage.ts index e61ef120..8382f4c1 100644 --- a/src/storage/client_side_storage.ts +++ b/src/storage/client_side_storage.ts @@ -56,7 +56,57 @@ export async function openClientSideStorage(): Promise { }; openRequest.onsuccess = () => { const db = openRequest.result; - resolve(ClientSideStorage.create(db)); + fixOldFiles(db).then(() => { + resolve(ClientSideStorage.create(db)); + }) + }; + }); +} + +// The following function allows Alan and Liz to load older projects. +// TODO(lizlooney): Remove this function. +async function fixOldFiles(db: IDBDatabase): Promise { + return new Promise((resolve, reject) => { + const transaction = db.transaction([FILES_STORE_NAME], 'readwrite'); + transaction.oncomplete = () => { + resolve(); + }; + transaction.onabort = () => { + console.log('IndexedDB transaction aborted.'); + reject(new Error('IndexedDB transaction aborted.')); + }; + const filesObjectStore = transaction.objectStore(FILES_STORE_NAME); + const openCursorRequest = filesObjectStore.openCursor(); + openCursorRequest.onerror = () => { + console.log('IndexedDB openCursor request failed. openCursorRequest.error is...'); + console.log(openCursorRequest.error); + reject(new Error('IndexedDB openCursor request failed.')); + }; + openCursorRequest.onsuccess = () => { + const cursor = openCursorRequest.result; + if (cursor) { + const value = cursor.value; + if (!value.path.startsWith('/projects/')) { + const oldFilePath = value.path; + value.path = '/projects/' + value.path; + const putRequest = filesObjectStore.put(value); + putRequest.onerror = () => { + console.log('IndexedDB put request failed. putRequest.error is...'); + console.log(putRequest.error); + throw new Error('IndexedDB put request failed.'); + }; + const deleteRequest = filesObjectStore.delete(oldFilePath); + deleteRequest.onerror = () => { + console.log('IndexedDB delete request failed. deleteRequest.error is...'); + console.log(deleteRequest.error); + throw new Error('IndexedDB delete request failed.'); + }; + } + cursor.continue(); + } else { + // The cursor is done. We have finished reading all the files. + resolve(); + } }; }); } @@ -124,13 +174,12 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async listFilePaths(opt_filePathRegexPattern?: string): Promise { - - const regExp = opt_filePathRegexPattern - ? new RegExp(opt_filePathRegexPattern) - : null; + async list(path: string): Promise { + if (!path.endsWith('/')) { + path += '/'; + } return new Promise((resolve, reject) => { - const filePaths: string[] = []; + const resultsSet: Set = new Set(); const openKeyCursorRequest = this.db.transaction([FILES_STORE_NAME], 'readonly') .objectStore(FILES_STORE_NAME) .openKeyCursor(); @@ -143,18 +192,122 @@ class ClientSideStorage implements commonStorage.Storage { const cursor = openKeyCursorRequest.result; if (cursor && cursor.key) { const filePath: string = cursor.key as string; - if (!regExp || regExp.test(filePath)) { - filePaths.push(filePath); + if (filePath.startsWith(path)) { + const relativePath = filePath.substring(path.length); + const slash = relativePath.indexOf('/'); + const result = (slash != -1) + ? relativePath.substring(0, slash + 1) // Include the trailing slash. + : relativePath; + resultsSet.add(result); } cursor.continue(); } else { // The cursor is done. We have finished reading all the files. - resolve(filePaths); + resolve([...resultsSet]); } }; }); } + async rename(oldPath: string, newPath: string): Promise { + if (oldPath.endsWith('/')) { + return this.renameDirectory(oldPath, newPath); + } + return this.renameFile(oldPath, newPath); + } + + private async renameDirectory(oldPath: string, newPath: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([FILES_STORE_NAME], 'readwrite'); + transaction.oncomplete = () => { + resolve(); + }; + transaction.onabort = () => { + console.log('IndexedDB transaction aborted.'); + reject(new Error('IndexedDB transaction aborted.')); + }; + const filesObjectStore = transaction.objectStore(FILES_STORE_NAME); + const openCursorRequest = filesObjectStore.openCursor(); + openCursorRequest.onerror = () => { + console.log('IndexedDB openCursor request failed. openCursorRequest.error is...'); + console.log(openCursorRequest.error); + throw new Error('IndexedDB openCursor request failed.'); + }; + openCursorRequest.onsuccess = () => { + const cursor = openCursorRequest.result; + if (cursor) { + const value = cursor.value; + if (value.path.startsWith(oldPath)) { + const relativePath = value.path.substring(oldPath.length); + const oldFilePath = value.path; + value.path = newPath + relativePath; + const putRequest = filesObjectStore.put(value); + putRequest.onerror = () => { + console.log('IndexedDB put request failed. putRequest.error is...'); + console.log(putRequest.error); + throw new Error('IndexedDB put request failed.'); + }; + putRequest.onsuccess = () => { + const deleteRequest = filesObjectStore.delete(oldFilePath); + deleteRequest.onerror = () => { + console.log('IndexedDB delete request failed. deleteRequest.error is...'); + console.log(deleteRequest.error); + throw new Error('IndexedDB delete request failed.'); + }; + } + } + cursor.continue(); + } else { + // The cursor is done. We have finished reading all the files. + resolve(); + } + }; + }); + } + + private async renameFile(oldPath: string, newPath: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([FILES_STORE_NAME], 'readwrite'); + transaction.oncomplete = () => { + resolve(); + }; + transaction.onabort = () => { + console.log('IndexedDB transaction aborted.'); + reject(new Error('IndexedDB transaction aborted.')); + }; + const filesObjectStore = transaction.objectStore(FILES_STORE_NAME); + const getRequest = filesObjectStore.get(oldPath); + getRequest.onerror = () => { + console.log('IndexedDB get request failed. getRequest.error is...'); + console.log(getRequest.error); + throw new Error('IndexedDB get request failed.'); + }; + getRequest.onsuccess = () => { + if (getRequest.result === undefined) { + console.log('IndexedDB get request succeeded, but the file does not exist.'); + throw new Error('IndexedDB get request succeeded, but the file does not exist.'); + return; + } + const value = getRequest.result; + value.path = newPath; + const putRequest = filesObjectStore.put(value); + putRequest.onerror = () => { + console.log('IndexedDB put request failed. putRequest.error is...'); + console.log(putRequest.error); + throw new Error('IndexedDB put request failed.'); + }; + putRequest.onsuccess = () => { + const deleteRequest = filesObjectStore.delete(oldPath); + deleteRequest.onerror = () => { + console.log('IndexedDB delete request failed. deleteRequest.error is...'); + console.log(deleteRequest.error); + throw new Error('IndexedDB delete request failed.'); + }; + }; + }; + }); + } + async fetchFileContentText(filePath: string): Promise { return new Promise((resolve, reject) => { const getRequest = this.db.transaction([FILES_STORE_NAME], 'readonly') @@ -213,7 +366,52 @@ class ClientSideStorage implements commonStorage.Storage { }); } - async deleteFile(filePath: string): Promise { + async delete(path: string): Promise { + if (path.endsWith('/')) { + return this.deleteDirectory(path); + } + return this.deleteFile(path); + } + + private async deleteDirectory(path: string): Promise { + return new Promise((resolve, reject) => { + const transaction = this.db.transaction([FILES_STORE_NAME], 'readwrite'); + transaction.oncomplete = () => { + resolve(); + }; + transaction.onabort = () => { + console.log('IndexedDB transaction aborted.'); + reject(new Error('IndexedDB transaction aborted.')); + }; + const filesObjectStore = transaction.objectStore(FILES_STORE_NAME); + const openKeyCursorRequest = filesObjectStore.openKeyCursor(); + openKeyCursorRequest.onerror = () => { + console.log('IndexedDB openKeyCursor request failed. openKeyCursorRequest.error is...'); + console.log(openKeyCursorRequest.error); + throw new Error('IndexedDB openKeyCursor request failed.'); + }; + openKeyCursorRequest.onsuccess = () => { + const cursor = openKeyCursorRequest.result; + if (cursor && cursor.key) { + const filePath: string = cursor.key as string; + if (filePath.startsWith(path)) { + const deleteRequest = filesObjectStore.delete(filePath); + deleteRequest.onerror = () => { + console.log('IndexedDB delete request failed. deleteRequest.error is...'); + console.log(deleteRequest.error); + throw new Error('IndexedDB delete request failed.'); + }; + } + cursor.continue(); + } else { + // The cursor is done. We have finished reading all the files. + resolve(); + } + }; + }); + } + + private async deleteFile(filePath: string): Promise { return new Promise((resolve, reject) => { const transaction = this.db.transaction([FILES_STORE_NAME], 'readwrite'); transaction.oncomplete = () => { diff --git a/src/storage/common_storage.ts b/src/storage/common_storage.ts index 96b70e4e..13d3dd09 100644 --- a/src/storage/common_storage.ts +++ b/src/storage/common_storage.ts @@ -28,11 +28,13 @@ export interface Storage { // Functions for storing files. - listFilePaths(opt_filePathRegexPattern?: string): Promise; + list(path: string): Promise; + + rename(oldPath: string, newPath: string): Promise; fetchFileContentText(filePath: string): Promise; saveFile(filePath: string, fileContentText: string): Promise; - deleteFile(filePath: string): Promise; + delete(path: string): Promise; } diff --git a/src/storage/names.ts b/src/storage/names.ts index 04d5c1c4..e53e6ea7 100644 --- a/src/storage/names.ts +++ b/src/storage/names.ts @@ -25,7 +25,9 @@ import * as storageModule from './module'; /** * Paths and file names for Blocks Projects * - * Files in a project are stored in a directory whose name is the project name. All files have + * All projects are stored in a directory called '/projects/'. + * + * Files in a project are stored in a subdirectory whose name is the project name. All files have * the extension '.json' and contain JSON text. * * Project information is stored in a file called 'project.info.json'. @@ -43,8 +45,8 @@ import * as storageModule from './module'; * zero or more mechanisms, with the extension '.mechanism.json' * zero or more opmodes, with the extension '.opmode.json' * - * The file path of the project info file is /project.info.json. - * The file path of a module is /..json. + * The file path of the project info file is /projects//project.info.json. + * The file path of a module is /projects//..json. */ // The class name of the Robot module that is created automatically when a new project is created. @@ -54,7 +56,7 @@ export const CLASS_NAME_ROBOT = 'Robot'; export const CLASS_NAME_TELEOP = 'Teleop'; // The extension of all JSON files is .json. -export const JSON_FILE_EXTENSION = '.json'; +const JSON_FILE_EXTENSION = '.json'; // The extension of a downloaded project is .blocks. export const UPLOAD_DOWNLOAD_FILE_EXTENSION = '.blocks'; @@ -62,9 +64,6 @@ export const UPLOAD_DOWNLOAD_FILE_EXTENSION = '.blocks'; // The file name of the project info file. const PROJECT_INFO_FILE_NAME = 'project.info.json'; -// The file name of the project info file. -const ROBOT_MODULE_FILE_NAME = 'Robot.robot.json'; - // A project name starts with an uppercase letter, followed by alphanumeric characters. const REGEX_PROJECT_NAME_PART = '[A-Z][A-Za-z0-9]*'; @@ -77,15 +76,17 @@ const REGEX_CLASS_NAME = '^' + REGEX_CLASS_NAME_PART + '$' // The module type of a module path is either .robot, .mechanism, or .opmode. const REGEX_MODULE_TYPE_PART = '\.(robot|mechanism|opmode)'; -// This regex is used to match the robot module path in any project. -export const REGEX_ROBOT_MODULE_PATH = '^' + REGEX_PROJECT_NAME_PART + '/' + escapeRegExp(ROBOT_MODULE_FILE_NAME) + '$'; +export const PROJECTS_DIRECTORY_PATH = '/projects/'; -// This regex is used to extract the project name and file name from a file path. -const REGEX_FILE_PATH = '^(' + REGEX_PROJECT_NAME_PART + ')/(.*' + escapeRegExp(JSON_FILE_EXTENSION) + ')$'; +// This regex is used to extract the project name from a file path. +const REGEX_FILE_PATH = '^' + escapeRegExp(PROJECTS_DIRECTORY_PATH) + + '(' + REGEX_PROJECT_NAME_PART + ')/.*' + escapeRegExp(JSON_FILE_EXTENSION) + '$'; // This regex is used to extract the class name from a module path. -const REGEX_MODULE_PATH = '^' + REGEX_PROJECT_NAME_PART + '/(' + REGEX_CLASS_NAME_PART + ')' + - REGEX_MODULE_TYPE_PART + escapeRegExp(JSON_FILE_EXTENSION) + '$'; +const REGEX_MODULE_PATH = '^' + escapeRegExp(PROJECTS_DIRECTORY_PATH) + + REGEX_PROJECT_NAME_PART + '/' + + '(' + REGEX_CLASS_NAME_PART + ')' + REGEX_MODULE_TYPE_PART + escapeRegExp(JSON_FILE_EXTENSION) + + '$'; // This regex is used to extract the class name from a module file name. const REGEX_MODULE_FILE_NAME = '^(' + REGEX_CLASS_NAME_PART + ')' + @@ -134,33 +135,24 @@ export function snakeCaseToPascalCase(snakeCaseName: string): string { } /** - * Returns a regex pattern that matches all file paths in the given project. - */ -export function makeFilePathRegexPattern(projectName: string): string { - return '^' + escapeRegExp(projectName) + '/' + - '.*' + escapeRegExp(JSON_FILE_EXTENSION) + '$'; -} - -/** - * Returns a regex pattern that matches all module paths in the given project. + * Escapes the given text so it can be used literally in a regular expression. */ -export function makeModulePathRegexPattern(projectName: string): string { - return '^' + escapeRegExp(projectName) + '/' + - REGEX_CLASS_NAME_PART + REGEX_MODULE_TYPE_PART + escapeRegExp(JSON_FILE_EXTENSION) + '$'; +function escapeRegExp(text: string): string { + return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /** - * Escapes the given text so it can be used literally in a regular expression. + * Returns the project directory path for the given project name. */ -function escapeRegExp(text: string): string { - return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +export function makeProjectDirectoryPath(projectName: string): string { + return PROJECTS_DIRECTORY_PATH + projectName + '/'; } /** * Returns the file path for the given project name and file name. */ export function makeFilePath(projectName: string, fileName: string): string { - return projectName + '/' + fileName; + return makeProjectDirectoryPath(projectName) + fileName; } /** @@ -197,18 +189,6 @@ export function getProjectName(filePath: string): string { return result[1]; } -/** - * Returns the file name for given file path. - */ -export function getFileName(filePath: string): string { - const regex = new RegExp(REGEX_FILE_PATH); - const result = regex.exec(filePath) - if (!result) { - throw new Error('Unable to extract the file name from "' + filePath + '"'); - } - return result[2]; -} - /** * Returns true if the given file name is a valid module file name. */ diff --git a/src/storage/project.ts b/src/storage/project.ts index 1fc22afc..7292fbd1 100644 --- a/src/storage/project.ts +++ b/src/storage/project.ts @@ -32,7 +32,7 @@ import { upgradeProjectIfNecessary } from './upgrade_project'; export type Project = { projectName: string, // For example, WackyWheelerRobot robot: storageModule.Robot, - mechanisms: storageModule.Mechanism[] + mechanisms: storageModule.Mechanism[], opModes: storageModule.OpMode[], }; @@ -47,12 +47,16 @@ type ProjectInfo = { * Returns the list of project names. */ export async function listProjectNames(storage: commonStorage.Storage): Promise { - const filePathRegexPattern = storageNames.REGEX_ROBOT_MODULE_PATH; - const robotModulePaths: string[] = await storage.listFilePaths(filePathRegexPattern); + const projectDirectoryNames: string[] = await storage.list(storageNames.PROJECTS_DIRECTORY_PATH); const projectNames: string[] = []; - for (const robotModulePath of robotModulePaths) { - projectNames.push(storageNames.getProjectName(robotModulePath)) + for (const projectDirectoryName of projectDirectoryNames) { + if (projectDirectoryName.endsWith('/')) { + // TODO(lizlooney): Should we check that the Robot.robot.json and project.info.json files + // exist in the directory? + const projectName = projectDirectoryName.slice(0, projectDirectoryName.length - 1); + projectNames.push(projectName); + } } return projectNames; } @@ -64,14 +68,18 @@ export async function fetchProject( storage: commonStorage.Storage, projectName: string): Promise { await upgradeProjectIfNecessary(storage, projectName); - const modulePaths: string[] = await storage.listFilePaths( - storageNames.makeModulePathRegexPattern(projectName)); + const projectFileNames: string[] = await storage.list( + storageNames.makeProjectDirectoryPath(projectName)); let project: Project | null = null; - const mechanisms: storageModule.Mechanism[] = [] + const mechanisms: storageModule.Mechanism[] = []; const opModes: storageModule.OpMode[] = []; - for (const modulePath of modulePaths) { + for (const projectFileName of projectFileNames) { + if (!storageNames.isValidModuleFileName(projectFileName)) { + continue; + } + const modulePath = storageNames.makeFilePath(projectName, projectFileName); const moduleContentText = await storage.fetchFileContentText(modulePath); const moduleContent: storageModuleContent.ModuleContent = storageModuleContent.parseModuleContentText(moduleContentText); @@ -141,7 +149,9 @@ export async function createProject( */ export async function renameProject( storage: commonStorage.Storage, projectName: string, newProjectName: string): Promise { - await renameOrCopyProject(storage, projectName, newProjectName, true); + const oldPath = storageNames.makeProjectDirectoryPath(projectName); + const newPath = storageNames.makeProjectDirectoryPath(newProjectName); + await storage.rename(oldPath, newPath); } /** @@ -153,28 +163,14 @@ export async function renameProject( */ export async function copyProject( storage: commonStorage.Storage, projectName: string, newProjectName: string): Promise { - await renameOrCopyProject(storage, projectName, newProjectName, false); -} - -async function renameOrCopyProject( - storage: commonStorage.Storage, projectName: string, newProjectName: string, - rename: boolean): Promise { - const modulePaths: string[] = await storage.listFilePaths( - storageNames.makeModulePathRegexPattern(projectName)); + const projectFileNames: string[] = await storage.list( + storageNames.makeProjectDirectoryPath(projectName)); - for (const modulePath of modulePaths) { - const className = storageNames.getClassName(modulePath); - const moduleType = storageNames.getModuleType(modulePath); - const newModulePath = storageNames.makeModulePath(newProjectName, className, moduleType); - const moduleContentText = await storage.fetchFileContentText(modulePath); - await storage.saveFile(newModulePath, moduleContentText); - if (rename) { - await storage.deleteFile(modulePath); - } - } - await saveProjectInfo(storage, newProjectName); - if (rename) { - await deleteProjectInfo(storage, projectName); + for (const projectFileName of projectFileNames) { + const filePath = storageNames.makeFilePath(projectName, projectFileName); + const newFilePath = storageNames.makeFilePath(newProjectName, projectFileName); + const fileContentText = await storage.fetchFileContentText(filePath); + await storage.saveFile(newFilePath, fileContentText); } } @@ -186,13 +182,7 @@ async function renameOrCopyProject( */ export async function deleteProject( storage: commonStorage.Storage, projectName: string): Promise { - const modulePaths: string[] = await storage.listFilePaths( - storageNames.makeModulePathRegexPattern(projectName)); - - for (const modulePath of modulePaths) { - await storage.deleteFile(modulePath); - } - await deleteProjectInfo(storage, projectName); + await storage.delete(storageNames.makeProjectDirectoryPath(projectName)); } /** @@ -254,7 +244,7 @@ export async function removeModuleFromProject( project.opModes = project.opModes.filter(o => o.modulePath !== modulePath); break; } - await storage.deleteFile(modulePath); + await storage.delete(modulePath); await saveProjectInfo(storage, project.projectName); } } @@ -269,14 +259,36 @@ export async function removeModuleFromProject( */ export async function renameModuleInProject( storage: commonStorage.Storage, project: Project, newClassName: string, oldModulePath: string): Promise { - const module = findModuleByModulePath(project, oldModulePath); - if (!module) { + const oldModule = findModuleByModulePath(project, oldModulePath); + if (!oldModule) { throw new Error('Failed to find module with path ' + oldModulePath); } - if (module.moduleType == storageModule.ModuleType.ROBOT) { + if (oldModule.moduleType == storageModule.ModuleType.ROBOT) { throw new Error('Renaming the robot module is not allowed.'); } - return await renameOrCopyModule(storage, project, newClassName, module, true); + const newModulePath = storageNames.makeModulePath(project.projectName, newClassName, oldModule.moduleType); + await storage.rename(oldModulePath, newModulePath); + + // Update the project's mechanisms or opModes. + switch (oldModule.moduleType) { + case storageModule.ModuleType.MECHANISM: + const mechanism = project.mechanisms.find(m => m.modulePath === oldModule.modulePath); + if (mechanism) { + mechanism.modulePath = newModulePath; + mechanism.className = newClassName; + } + break; + case storageModule.ModuleType.OPMODE: + const opMode = project.opModes.find(o => o.modulePath === oldModule.modulePath); + if (opMode) { + opMode.modulePath = newModulePath; + opMode.className = newClassName; + } + break; + } + await saveProjectInfo(storage, project.projectName); + + return newModulePath; } /** @@ -289,65 +301,37 @@ export async function renameModuleInProject( */ export async function copyModuleInProject( storage: commonStorage.Storage, project: Project, newClassName: string, oldModulePath: string): Promise { - const module = findModuleByModulePath(project, oldModulePath); - if (!module) { + const oldModule = findModuleByModulePath(project, oldModulePath); + if (!oldModule) { throw new Error('Failed to find module with path ' + oldModulePath); } - if (module.moduleType == storageModule.ModuleType.ROBOT) { + if (oldModule.moduleType == storageModule.ModuleType.ROBOT) { throw new Error('Copying the robot module is not allowed.'); } - return await renameOrCopyModule(storage, project, newClassName, module, false); -} - -async function renameOrCopyModule( - storage: commonStorage.Storage, project: Project, newClassName: string, - oldModule: storageModule.Module, rename: boolean): Promise { const newModulePath = storageNames.makeModulePath(project.projectName, newClassName, oldModule.moduleType); + + // Change the ids in the module. let moduleContentText = await storage.fetchFileContentText(oldModule.modulePath); - if (!rename) { - // Change the ids in the module. - const moduleContent = storageModuleContent.parseModuleContentText(moduleContentText); - moduleContent.changeIds(); - moduleContentText = moduleContent.getModuleContentText(); - } + const moduleContent = storageModuleContent.parseModuleContentText(moduleContentText); + moduleContent.changeIds(); + moduleContentText = moduleContent.getModuleContentText(); + await storage.saveFile(newModulePath, moduleContentText); - if (rename) { - // For rename, delete the old module. - await storage.deleteFile(oldModule.modulePath); - // Update the project's mechanisms or opModes. - switch (oldModule.moduleType) { - case storageModule.ModuleType.MECHANISM: - const mechanism = project.mechanisms.find(m => m.modulePath === oldModule.modulePath); - if (mechanism) { - mechanism.modulePath = newModulePath; - mechanism.className = newClassName; - } - break; - case storageModule.ModuleType.OPMODE: - const opMode = project.opModes.find(o => o.modulePath === oldModule.modulePath); - if (opMode) { - opMode.modulePath = newModulePath; - opMode.className = newClassName; - } - break; - } - } else { // copy - // Update the project's mechanisms or opModes. - const newModule = { - modulePath: newModulePath, - moduleType: oldModule.moduleType, - projectName: project.projectName, - className: newClassName - }; - switch (oldModule.moduleType) { - case storageModule.ModuleType.MECHANISM: - project.mechanisms.push(newModule as storageModule.Mechanism); - break; - case storageModule.ModuleType.OPMODE: - project.opModes.push(newModule as storageModule.OpMode); - break; - } + // Update the project's mechanisms or opModes. + const newModule = { + modulePath: newModulePath, + moduleType: oldModule.moduleType, + projectName: project.projectName, + className: newClassName + }; + switch (oldModule.moduleType) { + case storageModule.ModuleType.MECHANISM: + project.mechanisms.push(newModule as storageModule.Mechanism); + break; + case storageModule.ModuleType.OPMODE: + project.opModes.push(newModule as storageModule.OpMode); + break; } await saveProjectInfo(storage, project.projectName); @@ -423,12 +407,12 @@ export function findModuleByModulePath(project: Project, modulePath: string): st */ export async function downloadProject( storage: commonStorage.Storage, projectName: string): Promise { - const filePaths: string[] = await storage.listFilePaths( - storageNames.makeFilePathRegexPattern(projectName)); + const fileNames: string[] = await storage.list( + storageNames.makeProjectDirectoryPath(projectName)); const fileNameToFileContentText: {[fileName: string]: string} = {}; // value is file content text - for (const filePath of filePaths) { - const fileName = storageNames.getFileName(filePath); + for (const fileName of fileNames) { + const filePath = storageNames.makeFilePath(projectName, fileName); const fileContentText = await storage.fetchFileContentText(filePath); fileNameToFileContentText[fileName] = fileContentText; } @@ -539,12 +523,6 @@ function parseProjectInfoContentText(projectInfoContentText: string): ProjectInf return projectInfo; } -async function deleteProjectInfo( - storage: commonStorage.Storage, projectName: string): Promise { - const projectInfoPath = storageNames.makeProjectInfoPath(projectName); - await storage.deleteFile(projectInfoPath); -} - export async function fetchProjectInfo( storage: commonStorage.Storage, projectName: string): Promise { const projectInfoPath = storageNames.makeProjectInfoPath(projectName);