Merge from vscode 52dcb723a39ae75bee1bd56b3312d7fcdc87aeed (#6719)

This commit is contained in:
Anthony Dresser
2019-08-12 21:31:51 -07:00
committed by GitHub
parent 00250839fc
commit 7eba8c4c03
616 changed files with 9472 additions and 7087 deletions

View File

@@ -106,20 +106,15 @@ export class BackupFilesModel implements IBackupFilesModel {
export class BackupFileService implements IBackupFileService {
_serviceBrand: ServiceIdentifier<IBackupFileService>;
_serviceBrand!: ServiceIdentifier<IBackupFileService>;
private impl: IBackupFileService;
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IFileService fileService: IFileService
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
@IFileService protected fileService: IFileService
) {
const backupWorkspaceResource = environmentService.configuration.backupWorkspaceResource;
if (backupWorkspaceResource) {
this.impl = new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, fileService);
} else {
this.impl = new InMemoryBackupFileService(this.hashPath);
}
this.initialize();
}
protected hashPath(resource: URI): string {
@@ -128,9 +123,25 @@ export class BackupFileService implements IBackupFileService {
return hash(str).toString(16);
}
initialize(backupWorkspaceResource: URI): void {
private initialize(): void {
const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource;
if (backupWorkspaceResource) {
this.impl = new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService);
} else {
this.impl = new InMemoryBackupFileService(this.hashPath);
}
}
reinitialize(): void {
// Re-init implementation (unless we are running in-memory)
if (this.impl instanceof BackupFileServiceImpl) {
this.impl.initialize(backupWorkspaceResource);
const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource;
if (backupWorkspaceResource) {
this.impl.initialize(backupWorkspaceResource);
} else {
this.impl = new InMemoryBackupFileService(this.hashPath);
}
}
}
@@ -177,15 +188,15 @@ class BackupFileServiceImpl implements IBackupFileService {
private static readonly PREAMBLE_META_SEPARATOR = ' '; // using a character that is know to be escaped in a URI as separator
private static readonly PREAMBLE_MAX_LENGTH = 10000;
_serviceBrand: ServiceIdentifier<IBackupFileService>;
_serviceBrand!: ServiceIdentifier<IBackupFileService>;
private backupWorkspacePath: URI;
private backupWorkspacePath!: URI;
private isShuttingDown: boolean;
private ioOperationQueues: ResourceQueue; // queue IO operations to ensure write order
private ready: Promise<IBackupFilesModel>;
private model: IBackupFilesModel;
private ready!: Promise<IBackupFilesModel>;
private model!: IBackupFilesModel;
constructor(
backupWorkspaceResource: URI,
@@ -201,10 +212,10 @@ class BackupFileServiceImpl implements IBackupFileService {
initialize(backupWorkspaceResource: URI): void {
this.backupWorkspacePath = backupWorkspaceResource;
this.ready = this.init();
this.ready = this.doInitialize();
}
private init(): Promise<IBackupFilesModel> {
private doInitialize(): Promise<IBackupFilesModel> {
this.model = new BackupFilesModel(this.fileService);
return this.model.resolve(this.backupWorkspacePath);
@@ -323,7 +334,7 @@ class BackupFileServiceImpl implements IBackupFileService {
return contents.substr(0, newLineIndex);
}
throw new Error(`Could not find ${JSON.stringify(matchingString)} in first ${maximumBytesToRead} bytes of ${file}`);
throw new Error(`Backup: Could not find ${JSON.stringify(matchingString)} in first ${maximumBytesToRead} bytes of ${file}`);
}
async resolveBackupContent<T extends object>(backup: URI): Promise<IResolvedBackup<T>> {
@@ -357,9 +368,7 @@ class BackupFileServiceImpl implements IBackupFileService {
const content = await this.fileService.readFileStream(backup);
const factory = await createTextBufferFactoryFromStream(content.value, metaPreambleFilter);
// Trigger read for meta data extraction from the filter above
factory.getFirstLineText(1);
// Extract meta data (if any)
let meta: T | undefined;
const metaStartIndex = metaRaw.indexOf(BackupFileServiceImpl.PREAMBLE_META_SEPARATOR);
if (metaStartIndex !== -1) {
@@ -370,6 +379,15 @@ class BackupFileServiceImpl implements IBackupFileService {
}
}
// We have seen reports (e.g. https://github.com/microsoft/vscode/issues/78500) where
// if VSCode goes down while writing the backup file, the file can turn empty because
// it always first gets truncated and then written to. In this case, we will not find
// the meta-end marker ('\n') and as such the backup can only be invalid. We bail out
// here if that is the case.
if (!metaEndFound) {
throw new Error(`Backup: Could not find meta end marker in ${backup}. The file is probably corrupt.`);
}
return { value: factory, meta };
}
@@ -380,7 +398,7 @@ class BackupFileServiceImpl implements IBackupFileService {
export class InMemoryBackupFileService implements IBackupFileService {
_serviceBrand: ServiceIdentifier<IBackupFileService>;
_serviceBrand!: ServiceIdentifier<IBackupFileService>;
private backups: Map<string, ITextSnapshot> = new Map();
@@ -440,4 +458,4 @@ export class InMemoryBackupFileService implements IBackupFileService {
toBackupResource(resource: URI): URI {
return URI.file(join(resource.scheme, this.hashPath(resource)));
}
}
}

View File

@@ -7,13 +7,14 @@ import { BackupFileService as CommonBackupFileService } from 'vs/workbench/servi
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import * as crypto from 'crypto';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
export class BackupFileService extends CommonBackupFileService {
protected hashPath(resource: URI): string {
return hashPath(resource);
}
}
/*
@@ -21,5 +22,8 @@ export class BackupFileService extends CommonBackupFileService {
*/
export function hashPath(resource: URI): string {
const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString();
return crypto.createHash('md5').update(str).digest('hex');
}
}
registerSingleton(IBackupFileService, BackupFileService);

View File

@@ -27,6 +27,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
import { BACKUPS } from 'vs/platform/environment/common/environment';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { VSBuffer } from 'vs/base/common/buffer';
const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice');
const appSettingsHome = path.join(userdataDir, 'User');
@@ -479,6 +480,28 @@ suite('BackupFileService', () => {
await testResolveBackup(fooBarFile, contents, meta, null);
});
test('should throw an error when restoring invalid backup', async () => {
const contents = 'test\nand more stuff';
await service.backupResource(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1);
const backup = await service.loadBackupResource(fooBarFile);
if (!backup) {
throw new Error('Unexpected missing backup');
}
await service.fileService.writeFile(backup, VSBuffer.fromString(''));
let err: Error;
try {
await service.resolveBackupContent<IBackupTestMetaData>(backup);
} catch (error) {
err = error;
}
assert.ok(err!);
});
async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) {
if (typeof expectedMeta === 'undefined') {
expectedMeta = meta;