|  | 
|  | 1 | +import { ResourceSaveOptions } from '@theia/core/lib/common/resource'; | 
|  | 2 | +import { Readable } from '@theia/core/lib/common/stream'; | 
|  | 3 | +import URI from '@theia/core/lib/common/uri'; | 
|  | 4 | +import { injectable } from '@theia/core/shared/inversify'; | 
|  | 5 | +import { | 
|  | 6 | + FileResource, | 
|  | 7 | + FileResourceOptions, | 
|  | 8 | + FileResourceResolver as TheiaFileResourceResolver, | 
|  | 9 | +} from '@theia/filesystem/lib/browser/file-resource'; | 
|  | 10 | +import { FileService } from '@theia/filesystem/lib/browser/file-service'; | 
|  | 11 | +import { | 
|  | 12 | + FileOperationError, | 
|  | 13 | + FileOperationResult, | 
|  | 14 | + FileStat, | 
|  | 15 | +} from '@theia/filesystem/lib/common/files'; | 
|  | 16 | +import * as PQueue from 'p-queue'; | 
|  | 17 | + | 
|  | 18 | +@injectable() | 
|  | 19 | +export class FileResourceResolver extends TheiaFileResourceResolver { | 
|  | 20 | + override async resolve(uri: URI): Promise<WriteQueuedFileResource> { | 
|  | 21 | + let stat: FileStat | undefined; | 
|  | 22 | + try { | 
|  | 23 | + stat = await this.fileService.resolve(uri); | 
|  | 24 | + } catch (e) { | 
|  | 25 | + if ( | 
|  | 26 | + !( | 
|  | 27 | + e instanceof FileOperationError && | 
|  | 28 | + e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND | 
|  | 29 | + ) | 
|  | 30 | + ) { | 
|  | 31 | + throw e; | 
|  | 32 | + } | 
|  | 33 | + } | 
|  | 34 | + if (stat && stat.isDirectory) { | 
|  | 35 | + throw new Error( | 
|  | 36 | + 'The given uri is a directory: ' + this.labelProvider.getLongName(uri) | 
|  | 37 | + ); | 
|  | 38 | + } | 
|  | 39 | + return new WriteQueuedFileResource(uri, this.fileService, { | 
|  | 40 | + shouldOverwrite: () => this.shouldOverwrite(uri), | 
|  | 41 | + shouldOpenAsText: (error) => this.shouldOpenAsText(uri, error), | 
|  | 42 | + }); | 
|  | 43 | + } | 
|  | 44 | +} | 
|  | 45 | + | 
|  | 46 | +class WriteQueuedFileResource extends FileResource { | 
|  | 47 | + private readonly writeQueue = new PQueue({ autoStart: true, concurrency: 1 }); | 
|  | 48 | + | 
|  | 49 | + constructor( | 
|  | 50 | + uri: URI, | 
|  | 51 | + fileService: FileService, | 
|  | 52 | + options: FileResourceOptions | 
|  | 53 | + ) { | 
|  | 54 | + super(uri, fileService, options); | 
|  | 55 | + const originalSaveContentChanges = this['saveContentChanges']; | 
|  | 56 | + if (originalSaveContentChanges) { | 
|  | 57 | + this['saveContentChanges'] = (changes, options) => { | 
|  | 58 | + return this.writeQueue.add(() => | 
|  | 59 | + originalSaveContentChanges.bind(this)(changes, options) | 
|  | 60 | + ); | 
|  | 61 | + }; | 
|  | 62 | + } | 
|  | 63 | + } | 
|  | 64 | + | 
|  | 65 | + protected override async doWrite( | 
|  | 66 | + content: string | Readable<string>, | 
|  | 67 | + options?: ResourceSaveOptions | 
|  | 68 | + ): Promise<void> { | 
|  | 69 | + return this.writeQueue.add(() => super.doWrite(content, options)); | 
|  | 70 | + } | 
|  | 71 | + | 
|  | 72 | + protected override async isInSync(): Promise<boolean> { | 
|  | 73 | + // Let all the write operations finish to update the version (mtime) before checking whether the resource is in sync. | 
|  | 74 | + // https://github.com/eclipse-theia/theia/issues/12327 | 
|  | 75 | + await this.writeQueue.onIdle(); | 
|  | 76 | + return super.isInSync(); | 
|  | 77 | + } | 
|  | 78 | +} | 
0 commit comments