IsolatedFileSystem.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. export default class IsolatedFileSystem extends Persistence.PlatformFileSystem{constructor(manager,path,embedderPath,domFileSystem,type){super(path,type);this._manager=manager;this._embedderPath=embedderPath;this._domFileSystem=domFileSystem;this._excludedFoldersSetting=Common.settings.createLocalSetting('workspaceExcludedFolders',{});this._excludedFolders=new Set(this._excludedFoldersSetting.get()[path]||[]);this._excludedEmbedderFolders=[];this._initialFilePaths=new Set();this._initialGitFolders=new Set();this._fileLocks=new Map();}
  2. static create(manager,path,embedderPath,type,name,rootURL){const domFileSystem=Host.InspectorFrontendHost.isolatedFileSystem(name,rootURL);if(!domFileSystem){return Promise.resolve((null));}
  3. const fileSystem=new IsolatedFileSystem(manager,path,embedderPath,domFileSystem,type);return fileSystem._initializeFilePaths().then(()=>fileSystem).catchException((null));}
  4. static errorMessage(error){return Common.UIString('File system error: %s',error.message);}
  5. _serializedFileOperation(path,operation){const promise=Promise.resolve(this._fileLocks.get(path)).then(()=>operation.call(null));this._fileLocks.set(path,promise);return promise;}
  6. getMetadata(path){let fulfill;const promise=new Promise(f=>fulfill=f);this._domFileSystem.root.getFile(path,undefined,fileEntryLoaded,errorHandler);return promise;function fileEntryLoaded(entry){entry.getMetadata(fulfill,errorHandler);}
  7. function errorHandler(error){const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' when getting file metadata \''+path);fulfill(null);}}
  8. initialFilePaths(){return this._initialFilePaths.valuesArray();}
  9. initialGitFolders(){return this._initialGitFolders.valuesArray();}
  10. embedderPath(){return this._embedderPath;}
  11. _initializeFilePaths(){let fulfill;const promise=new Promise(x=>fulfill=x);let pendingRequests=1;const boundInnerCallback=innerCallback.bind(this);this._requestEntries('',boundInnerCallback);return promise;function innerCallback(entries){for(let i=0;i<entries.length;++i){const entry=entries[i];if(!entry.isDirectory){if(this.isFileExcluded(entry.fullPath)){continue;}
  12. this._initialFilePaths.add(entry.fullPath.substr(1));}else{if(entry.fullPath.endsWith('/.git')){const lastSlash=entry.fullPath.lastIndexOf('/');const parentFolder=entry.fullPath.substring(1,lastSlash);this._initialGitFolders.add(parentFolder);}
  13. if(this.isFileExcluded(entry.fullPath+'/')){this._excludedEmbedderFolders.push(Common.ParsedURL.urlToPlatformPath(this.path()+entry.fullPath,Host.isWin()));continue;}
  14. ++pendingRequests;this._requestEntries(entry.fullPath,boundInnerCallback);}}
  15. if((--pendingRequests===0)){fulfill();}}}
  16. async _createFoldersIfNotExist(folderPath){let dirEntry=await new Promise(resolve=>this._domFileSystem.root.getDirectory(folderPath,undefined,resolve,()=>resolve(null)));if(dirEntry){return dirEntry;}
  17. const paths=folderPath.split('/');let activePath='';for(const path of paths){activePath=activePath+'/'+path;dirEntry=await this._innerCreateFolderIfNeeded(activePath);if(!dirEntry){return null;}}
  18. return dirEntry;}
  19. _innerCreateFolderIfNeeded(path){return new Promise(resolve=>{this._domFileSystem.root.getDirectory(path,{create:true},dirEntry=>resolve(dirEntry),error=>{const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' trying to create directory \''+path+'\'');resolve(null);});});}
  20. async createFile(path,name){const dirEntry=await this._createFoldersIfNotExist(path);if(!dirEntry){return null;}
  21. const fileEntry=await this._serializedFileOperation(path,createFileCandidate.bind(this,name||'NewFile'));if(!fileEntry){return null;}
  22. return fileEntry.fullPath.substr(1);function createFileCandidate(name,newFileIndex){return new Promise(resolve=>{const nameCandidate=name+(newFileIndex||'');dirEntry.getFile(nameCandidate,{create:true,exclusive:true},resolve,error=>{if(error.name==='InvalidModificationError'){resolve(createFileCandidate.call(this,name,(newFileIndex?newFileIndex+1:1)));return;}
  23. const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' when testing if file exists \''+(this.path()+'/'+path+'/'+nameCandidate)+'\'');resolve(null);});});}}
  24. deleteFile(path){let resolveCallback;const promise=new Promise(resolve=>resolveCallback=resolve);this._domFileSystem.root.getFile(path,undefined,fileEntryLoaded.bind(this),errorHandler.bind(this));return promise;function fileEntryLoaded(fileEntry){fileEntry.remove(fileEntryRemoved,errorHandler.bind(this));}
  25. function fileEntryRemoved(){resolveCallback(true);}
  26. function errorHandler(error){const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' when deleting file \''+(this.path()+'/'+path)+'\'');resolveCallback(false);}}
  27. requestFileBlob(path){return new Promise(resolve=>{this._domFileSystem.root.getFile(path,undefined,entry=>{entry.file(resolve,errorHandler.bind(this));},errorHandler.bind(this));function errorHandler(error){if(error.name==='NotFoundError'){resolve(null);return;}
  28. const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' when getting content for file \''+(this.path()+'/'+path)+'\'');resolve(null);}});}
  29. requestFileContent(path){return this._serializedFileOperation(path,()=>this._innerRequestFileContent(path));}
  30. async _innerRequestFileContent(path){const blob=await this.requestFileBlob(path);if(!blob){return{error:ls`Blob could not be loaded.`,isEncoded:false};}
  31. const reader=new FileReader();const extension=Common.ParsedURL.extractExtension(path);const encoded=Persistence.IsolatedFileSystem.BinaryExtensions.has(extension);const readPromise=new Promise(x=>reader.onloadend=x);if(encoded){reader.readAsBinaryString(blob);}else{reader.readAsText(blob);}
  32. await readPromise;if(reader.error){const error=ls`Can't read file: ${path}: ${reader.error}`;console.error(error);return{isEncoded:false,error};}
  33. let result=null;let error=null;try{result=(reader.result);}catch(e){result=null;error=ls`Can't read file: ${path}: ${e.message}`;}
  34. if(result===undefined||result===null){error=error||ls`Unknown error reading file: ${path}`;console.error(error);return{isEncoded:false,error};}
  35. return{isEncoded:encoded,content:encoded?btoa(result):result};}
  36. async setFileContent(path,content,isBase64){Host.userMetrics.actionTaken(Host.UserMetrics.Action.FileSavedInWorkspace);let callback;const innerSetFileContent=()=>{const promise=new Promise(x=>callback=x);this._domFileSystem.root.getFile(path,{create:true},fileEntryLoaded.bind(this),errorHandler.bind(this));return promise;};this._serializedFileOperation(path,innerSetFileContent);function fileEntryLoaded(entry){entry.createWriter(fileWriterCreated.bind(this),errorHandler.bind(this));}
  37. async function fileWriterCreated(fileWriter){fileWriter.onerror=errorHandler.bind(this);fileWriter.onwriteend=fileWritten;let blob;if(isBase64){blob=await(await fetch(`data:application/octet-stream;base64,${content}`)).blob();}else{blob=new Blob([content],{type:'text/plain'});}
  38. fileWriter.write(blob);function fileWritten(){fileWriter.onwriteend=callback;fileWriter.truncate(blob.size);}}
  39. function errorHandler(error){const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' when setting content for file \''+(this.path()+'/'+path)+'\'');callback();}}
  40. renameFile(path,newName,callback){newName=newName?newName.trim():newName;if(!newName||newName.indexOf('/')!==-1){callback(false);return;}
  41. let fileEntry;let dirEntry;this._domFileSystem.root.getFile(path,undefined,fileEntryLoaded.bind(this),errorHandler.bind(this));function fileEntryLoaded(entry){if(entry.name===newName){callback(false);return;}
  42. fileEntry=entry;fileEntry.getParent(dirEntryLoaded.bind(this),errorHandler.bind(this));}
  43. function dirEntryLoaded(entry){dirEntry=entry;dirEntry.getFile(newName,null,newFileEntryLoaded,newFileEntryLoadErrorHandler.bind(this));}
  44. function newFileEntryLoaded(entry){callback(false);}
  45. function newFileEntryLoadErrorHandler(error){if(error.name!=='NotFoundError'){callback(false);return;}
  46. fileEntry.moveTo(dirEntry,newName,fileRenamed,errorHandler.bind(this));}
  47. function fileRenamed(entry){callback(true,entry.name);}
  48. function errorHandler(error){const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' when renaming file \''+(this.path()+'/'+path)+'\' to \''+newName+'\'');callback(false);}}
  49. _readDirectory(dirEntry,callback){const dirReader=dirEntry.createReader();let entries=[];function innerCallback(results){if(!results.length){callback(entries.sort());}else{entries=entries.concat(toArray(results));dirReader.readEntries(innerCallback,errorHandler);}}
  50. function toArray(list){return Array.prototype.slice.call(list||[],0);}
  51. dirReader.readEntries(innerCallback,errorHandler);function errorHandler(error){const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' when reading directory \''+dirEntry.fullPath+'\'');callback([]);}}
  52. _requestEntries(path,callback){this._domFileSystem.root.getDirectory(path,undefined,innerCallback.bind(this),errorHandler);function innerCallback(dirEntry){this._readDirectory(dirEntry,callback);}
  53. function errorHandler(error){const errorMessage=IsolatedFileSystem.errorMessage(error);console.error(errorMessage+' when requesting entry \''+path+'\'');callback([]);}}
  54. _saveExcludedFolders(){const settingValue=this._excludedFoldersSetting.get();settingValue[this.path()]=this._excludedFolders.valuesArray();this._excludedFoldersSetting.set(settingValue);}
  55. addExcludedFolder(path){this._excludedFolders.add(path);this._saveExcludedFolders();this._manager.dispatchEventToListeners(Persistence.IsolatedFileSystemManager.Events.ExcludedFolderAdded,path);}
  56. removeExcludedFolder(path){this._excludedFolders.delete(path);this._saveExcludedFolders();this._manager.dispatchEventToListeners(Persistence.IsolatedFileSystemManager.Events.ExcludedFolderRemoved,path);}
  57. fileSystemRemoved(){const settingValue=this._excludedFoldersSetting.get();delete settingValue[this.path()];this._excludedFoldersSetting.set(settingValue);}
  58. isFileExcluded(folderPath){if(this._excludedFolders.has(folderPath)){return true;}
  59. const regex=this._manager.workspaceFolderExcludePatternSetting().asRegExp();return!!(regex&&regex.test(folderPath));}
  60. excludedFolders(){return this._excludedFolders;}
  61. searchInPath(query,progress){return new Promise(resolve=>{const requestId=this._manager.registerCallback(innerCallback);Host.InspectorFrontendHost.searchInPath(requestId,this._embedderPath,query);function innerCallback(files){resolve(files.map(path=>Common.ParsedURL.platformPathToURL(path)));progress.worked(1);}});}
  62. indexContent(progress){progress.setTotalWork(1);const requestId=this._manager.registerProgress(progress);Host.InspectorFrontendHost.indexPath(requestId,this._embedderPath,JSON.stringify(this._excludedEmbedderFolders));}
  63. mimeFromPath(path){return Common.ResourceType.mimeFromURL(path)||'text/plain';}
  64. canExcludeFolder(path){return!!path&&this.type()!=='overrides';}
  65. contentType(path){const extension=Common.ParsedURL.extractExtension(path);if(_styleSheetExtensions.has(extension)){return Common.resourceTypes.Stylesheet;}
  66. if(_documentExtensions.has(extension)){return Common.resourceTypes.Document;}
  67. if(ImageExtensions.has(extension)){return Common.resourceTypes.Image;}
  68. if(_scriptExtensions.has(extension)){return Common.resourceTypes.Script;}
  69. return BinaryExtensions.has(extension)?Common.resourceTypes.Other:Common.resourceTypes.Document;}
  70. tooltipForURL(url){const path=Common.ParsedURL.urlToPlatformPath(url,Host.isWin()).trimMiddle(150);return ls`Linked to ${path}`;}
  71. supportsAutomapping(){return this.type()!=='overrides';}}
  72. const _styleSheetExtensions=new Set(['css','scss','sass','less']);const _documentExtensions=new Set(['htm','html','asp','aspx','phtml','jsp']);const _scriptExtensions=new Set(['asp','aspx','c','cc','cljs','coffee','cpp','cs','dart','java','js','jsp','jsx','h','m','mjs','mm','py','sh','ts','tsx','ls']);const ImageExtensions=new Set(['jpeg','jpg','svg','gif','webp','png','ico','tiff','tif','bmp']);export const BinaryExtensions=new Set(['cmd','com','exe','a','ar','iso','tar','bz2','gz','lz','lzma','z','7z','apk','arc','cab','dmg','jar','pak','rar','zip','3gp','aac','aiff','flac','m4a','mmf','mp3','ogg','oga','raw','sln','wav','wma','webm','mkv','flv','vob','ogv','gifv','avi','mov','qt','mp4','m4p','m4v','mpg','mpeg','jpeg','jpg','gif','webp','png','ico','tiff','tif','bmp']);self.Persistence=self.Persistence||{};Persistence=Persistence||{};Persistence.IsolatedFileSystem=IsolatedFileSystem;Persistence.IsolatedFileSystem.BinaryExtensions=BinaryExtensions;