提交 0ff871ce 编写于 作者: B Benjamin Pasero

Hot Exit: properly deal with backups to files that got deleted (fixes #14054)

上级 2e77e6c9
......@@ -126,6 +126,11 @@ export interface IFileService {
*/
updateOptions(options: any): void;
/**
* Returns the preferred encoding to use for a given resource.
*/
getEncoding(resource: URI): string;
/**
* Frees up any resources occupied by this service.
*/
......
......@@ -59,6 +59,27 @@ export class BackupRestorer implements IWorkbenchContribution {
});
}
private doResolveOpenedBackups(backups: URI[]): TPromise<URI[]> {
const stacks = this.groupService.getStacksModel();
const restorePromises: TPromise<any>[] = [];
const unresolved: URI[] = [];
backups.forEach(backup => {
if (stacks.isOpen(backup)) {
if (backup.scheme === 'file') {
restorePromises.push(this.textModelResolverService.createModelReference(backup).then(null, () => unresolved.push(backup)));
} else if (backup.scheme === 'untitled') {
restorePromises.push(this.untitledEditorService.get(backup).resolve().then(null, () => unresolved.push(backup)));
}
} else {
unresolved.push(backup);
}
});
return TPromise.join(restorePromises).then(() => unresolved, () => unresolved);
}
private doOpenEditors(inputs: URI[]): TPromise<void> {
const stacks = this.groupService.getStacksModel();
const hasOpenedEditors = stacks.groups.length > 0;
......@@ -83,27 +104,6 @@ export class BackupRestorer implements IWorkbenchContribution {
return this.editorService.createInput({ resource });
}
private doResolveOpenedBackups(backups: URI[]): TPromise<URI[]> {
const stacks = this.groupService.getStacksModel();
const restorePromises: TPromise<any>[] = [];
const unresolved: URI[] = [];
backups.forEach(backup => {
if (stacks.isOpen(backup)) {
if (backup.scheme === 'file') {
restorePromises.push(this.textModelResolverService.createModelReference(backup).then(null, () => unresolved.push(backup)));
} else if (backup.scheme === 'untitled') {
restorePromises.push(this.untitledEditorService.get(backup).resolve().then(null, () => unresolved.push(backup)));
}
} else {
unresolved.push(backup);
}
});
return TPromise.join(restorePromises).then(() => unresolved, () => unresolved);
}
public getId(): string {
return 'vs.backup.backupRestorer';
}
......
......@@ -289,6 +289,10 @@ export class FileService implements IFileService {
this.raw.unwatchFileChanges(arg1);
}
public getEncoding(resource: uri): string {
return this.raw.getEncoding(resource);
}
public dispose(): void {
this.toUnbind = dispose(this.toUnbind);
......
......@@ -518,7 +518,7 @@ export class FileService implements IFileService {
});
}
private getEncoding(resource: uri, preferredEncoding?: string): string {
public getEncoding(resource: uri, preferredEncoding?: string): string {
let fileEncoding: string;
const override = this.getEncodingOverride(resource);
......
......@@ -19,11 +19,11 @@ import types = require('vs/base/common/types');
import { IModelContentChangedEvent, IRawText } from 'vs/editor/common/editorCommon';
import { IMode } from 'vs/editor/common/modes';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, IModelSaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, IModelSaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent } from 'vs/workbench/services/textfile/common/textfiles';
import { EncodingMode, EditorModel } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { IBackupFileService, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { IFileService, IFileStat, IFileOperationResult, FileOperationResult } from 'vs/platform/files/common/files';
import { IFileService, IFileStat, IFileOperationResult, FileOperationResult, IContent } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { IModeService } from 'vs/editor/common/services/modeService';
......@@ -196,7 +196,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Emit file change event
this._onDidStateChange.fire(StateChange.REVERTED);
}, (error) => {
}, error => {
// FileNotFound means the file got deleted meanwhile, so emit revert event because thats ok
if ((<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
......@@ -233,109 +233,135 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
// Resolve Content
return this.textFileService.resolveTextContent(this.resource, { acceptTextOnly: true, etag: etag, encoding: this.preferredEncoding }).then((content) => {
diag('load() - resolved content', this.resource, new Date());
// Telemetry
this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.resource.fsPath), path: anonymize(this.resource.fsPath) });
// Update our resolved disk stat model
const resolvedStat: IFileStat = {
resource: this.resource,
name: content.name,
mtime: content.mtime,
etag: content.etag,
isDirectory: false,
hasChildren: false,
children: void 0,
};
this.updateVersionOnDiskStat(resolvedStat);
return this.textFileService.resolveTextContent(this.resource, { acceptTextOnly: true, etag: etag, encoding: this.preferredEncoding }).then(content => this.loadWithContent(content), error => {
const result = (<IFileOperationResult>error).fileOperationResult;
// Keep the original encoding to not loose it when saving
const oldEncoding = this.contentEncoding;
this.contentEncoding = content.encoding;
// NotModified status is expected and can be handled gracefully
if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) {
this.setDirty(false); // Ensure we are not tracking a stale state
// Handle events if encoding changed
if (this.preferredEncoding) {
this.updatePreferredEncoding(this.contentEncoding); // make sure to reflect the real encoding of the file (never out of sync)
} else if (oldEncoding !== this.contentEncoding) {
this._onDidStateChange.fire(StateChange.ENCODING);
return TPromise.as<EditorModel>(this);
}
// Update Existing Model
if (this.textEditorModel) {
diag('load() - updated text editor model', this.resource, new Date());
// FileNotFound needs to be handled if we have a backup
if (result === FileOperationResult.FILE_NOT_FOUND) {
if (!this.textEditorModel && !this.createTextEditorModelPromise) {
return this.backupFileService.loadBackupResource(this.resource).then(backup => {
if (!!backup) {
const content: IContent = {
resource: this.resource,
name: paths.basename(this.resource.fsPath),
mtime: Date.now(),
etag: void 0,
value: '', /* will be filled later from backup */
encoding: this.fileService.getEncoding(this.resource)
};
return this.loadWithContent(content);
}
// Otherwise bubble up the error
return TPromise.wrapError(error);
}, ignoreError => TPromise.wrapError(error));
}
}
this.setDirty(false); // Ensure we are not tracking a stale state
// Otherwise bubble up the error
return TPromise.wrapError(error);
});
}
this.blockModelContentChange = true;
try {
this.updateTextEditorModel(content.value);
} finally {
this.blockModelContentChange = false;
}
private loadWithContent(content: IRawTextContent | IContent): TPromise<EditorModel> {
diag('load() - resolved content', this.resource, new Date());
return TPromise.as<EditorModel>(this);
}
// Telemetry
this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.resource.fsPath), path: anonymize(this.resource.fsPath) });
// Join an existing request to create the editor model to avoid race conditions
else if (this.createTextEditorModelPromise) {
diag('load() - join existing text editor model promise', this.resource, new Date());
// Update our resolved disk stat model
const resolvedStat: IFileStat = {
resource: this.resource,
name: content.name,
mtime: content.mtime,
etag: content.etag,
isDirectory: false,
hasChildren: false,
children: void 0,
};
this.updateVersionOnDiskStat(resolvedStat);
return this.createTextEditorModelPromise;
}
// Keep the original encoding to not loose it when saving
const oldEncoding = this.contentEncoding;
this.contentEncoding = content.encoding;
// Create New Model
else {
diag('load() - created text editor model', this.resource, new Date());
return this.backupFileService.loadBackupResource(this.resource).then(backupResource => {
let resolveBackupPromise: TPromise<string | IRawText>;
// Try get restore content, if there is an issue fallback silently to the original file's content
if (backupResource) {
resolveBackupPromise = this.textFileService.resolveTextContent(backupResource, BACKUP_FILE_RESOLVE_OPTIONS).then(backup => {
return this.backupFileService.parseBackupContent(backup);
}, error => content.value);
} else {
resolveBackupPromise = TPromise.as(content.value);
}
this.createTextEditorModelPromise = resolveBackupPromise.then(fileContent => {
return this.createTextEditorModel(fileContent, content.resource).then(() => {
this.createTextEditorModelPromise = null;
if (backupResource) {
this.makeDirty();
} else {
this.setDirty(false); // Ensure we are not tracking a stale state
}
this.toDispose.push(this.textEditorModel.onDidChangeRawContent((e: IModelContentChangedEvent) => this.onModelContentChanged(e)));
return this;
}, (error) => {
this.createTextEditorModelPromise = null;
return TPromise.wrapError(error);
});
});
// Handle events if encoding changed
if (this.preferredEncoding) {
this.updatePreferredEncoding(this.contentEncoding); // make sure to reflect the real encoding of the file (never out of sync)
} else if (oldEncoding !== this.contentEncoding) {
this._onDidStateChange.fire(StateChange.ENCODING);
}
return this.createTextEditorModelPromise;
});
}
}, (error) => {
// Update Existing Model
if (this.textEditorModel) {
diag('load() - updated text editor model', this.resource, new Date());
// NotModified status code is expected and can be handled gracefully
if ((<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_NOT_MODIFIED_SINCE) {
this.setDirty(false); // Ensure we are not tracking a stale state
this.setDirty(false); // Ensure we are not tracking a stale state
return TPromise.as<EditorModel>(this);
this.blockModelContentChange = true;
try {
this.updateTextEditorModel(content.value);
} finally {
this.blockModelContentChange = false;
}
// Otherwise bubble up the error
return TPromise.wrapError(error);
});
return TPromise.as<EditorModel>(this);
}
// Join an existing request to create the editor model to avoid race conditions
else if (this.createTextEditorModelPromise) {
diag('load() - join existing text editor model promise', this.resource, new Date());
return this.createTextEditorModelPromise;
}
// Create New Model
else {
diag('load() - created text editor model', this.resource, new Date());
return this.backupFileService.loadBackupResource(this.resource).then(backupResource => {
let resolveBackupPromise: TPromise<string | IRawText>;
// Try get restore content, if there is an issue fallback silently to the original file's content
if (backupResource) {
resolveBackupPromise = this.textFileService.resolveTextContent(backupResource, BACKUP_FILE_RESOLVE_OPTIONS).then(backup => {
return this.backupFileService.parseBackupContent(backup);
}, error => content.value);
} else {
resolveBackupPromise = TPromise.as(content.value);
}
this.createTextEditorModelPromise = resolveBackupPromise.then(fileContent => {
return this.createTextEditorModel(fileContent, content.resource).then(() => {
this.createTextEditorModelPromise = null;
if (backupResource) {
this.makeDirty();
} else {
this.setDirty(false); // Ensure we are not tracking a stale state
}
this.toDispose.push(this.textEditorModel.onDidChangeRawContent((e: IModelContentChangedEvent) => this.onModelContentChanged(e)));
return this;
}, error => {
this.createTextEditorModelPromise = null;
return TPromise.wrapError(error);
});
});
return this.createTextEditorModelPromise;
});
}
}
protected getOrCreateMode(modeService: IModeService, preferredModeIds: string, firstLineText?: string): TPromise<IMode> {
......@@ -561,7 +587,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Emit File Saved Event
this._onDidStateChange.fire(StateChange.SAVED);
}, (error) => {
}, error => {
diag(`doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource, new Date());
// Remove from pending saves
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册