提交 d44201fa 编写于 作者: B Benjamin Pasero

wire in ITextSnapshot for backup writing

上级 1d56aabe
......@@ -481,6 +481,32 @@ export function snapshotToString(snapshot: ITextSnapshot): string {
return chunks.join('');
}
/**
* Helper that wraps around a ITextSnapshot and allows to have a
* preamble that the read() method will return first.
*/
export class BufferedTextSnapshot implements ITextSnapshot {
private preambleHandled: boolean;
constructor(private snapshot: ITextSnapshot, private preamble: string) {
}
public read(): string {
let value = this.snapshot.read();
if (!this.preambleHandled) {
this.preambleHandled = true;
if (typeof value === 'string') {
value = this.preamble + value;
} else {
value = this.preamble;
}
}
return value;
}
}
/**
* Streamable content and meta information of a file.
*/
......
......@@ -72,14 +72,14 @@ export class BackupModelTracker implements IWorkbenchContribution {
// Do not backup when auto save after delay is configured
if (!this.configuredAutoSaveAfterDelay) {
const model = this.textFileService.models.get(event.resource);
this.backupFileService.backupResource(model.getResource(), model.getValue(), model.getVersionId()).done(null, errors.onUnexpectedError);
this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId()).done(null, errors.onUnexpectedError);
}
}
}
private onUntitledModelChanged(resource: Uri): void {
if (this.untitledEditorService.isDirty(resource)) {
this.untitledEditorService.loadOrCreate({ resource }).then(model => this.backupFileService.backupResource(resource, model.getValue(), model.getVersionId())).done(null, errors.onUnexpectedError);
this.untitledEditorService.loadOrCreate({ resource }).then(model => this.backupFileService.backupResource(resource, model.createSnapshot(), model.getVersionId())).done(null, errors.onUnexpectedError);
} else {
this.discardBackup(resource);
}
......
......@@ -318,7 +318,7 @@ class RenameFileAction extends BaseRenameAction {
const model = this.textFileService.models.get(d);
return this.backupFileService.backupResource(renamed, model.getValue(), model.getVersionId());
return this.backupFileService.backupResource(renamed, model.createSnapshot(), model.getVersionId());
}))
// 2. soft revert all dirty since we have backed up their contents
......
......@@ -968,7 +968,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop {
const model = this.textFileService.models.get(d);
return this.backupFileService.backupResource(moved, model.getValue(), model.getVersionId());
return this.backupFileService.backupResource(moved, model.createSnapshot(), model.getVersionId());
}))
// 2. soft revert all dirty since we have backed up their contents
......
......@@ -8,7 +8,7 @@
import Uri from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import { IResolveContentOptions, IUpdateContentOptions } from 'vs/platform/files/common/files';
import { IResolveContentOptions, IUpdateContentOptions, ITextSnapshot } from 'vs/platform/files/common/files';
import { ITextBufferFactory } from 'vs/editor/common/model';
export const IBackupFileService = createDecorator<IBackupFileService>('backupFileService');
......@@ -52,10 +52,10 @@ export interface IBackupFileService {
* Backs up a resource.
*
* @param resource The resource to back up.
* @param content The content of the resource.
* @param content The content of the resource as value or snapshot.
* @param versionId The version id of the resource to backup.
*/
backupResource(resource: Uri, content: string, versionId?: number): TPromise<void>;
backupResource(resource: Uri, content: string | ITextSnapshot, versionId?: number): TPromise<void>;
/**
* Gets a list of file backups for the current workspace.
......
......@@ -11,7 +11,7 @@ import * as pfs from 'vs/base/node/pfs';
import Uri from 'vs/base/common/uri';
import { ResourceQueue } from 'vs/base/common/async';
import { IBackupFileService, BACKUP_FILE_UPDATE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { IFileService } from 'vs/platform/files/common/files';
import { IFileService, ITextSnapshot, BufferedTextSnapshot, IFileStat } from 'vs/platform/files/common/files';
import { TPromise } from 'vs/base/common/winjs.base';
import { readToMatchingString } from 'vs/base/node/stream';
import { Range } from 'vs/editor/common/core/range';
......@@ -149,7 +149,7 @@ export class BackupFileService implements IBackupFileService {
});
}
public backupResource(resource: Uri, content: string, versionId?: number): TPromise<void> {
public backupResource(resource: Uri, content: string | ITextSnapshot, versionId?: number): TPromise<void> {
if (this.isShuttingDown) {
return TPromise.as(void 0);
}
......@@ -164,11 +164,21 @@ export class BackupFileService implements IBackupFileService {
return void 0; // return early if backup version id matches requested one
}
// Add metadata to top of file
content = `${resource.toString()}${BackupFileService.META_MARKER}${content}`;
return this.ioOperationQueues.queueFor(backupResource).queue(() => {
return this.fileService.updateContent(backupResource, content, BACKUP_FILE_UPDATE_OPTIONS).then(() => model.add(backupResource, versionId));
const preamble = `${resource.toString()}${BackupFileService.META_MARKER}`;
// Update content with value
let updateContentPromise: TPromise<IFileStat>;
if (typeof content === 'string') {
updateContentPromise = this.fileService.updateContent(backupResource, `${preamble}${content}`, BACKUP_FILE_UPDATE_OPTIONS);
}
// Update content with snapshot
else {
updateContentPromise = this.fileService.updateContent(backupResource, new BufferedTextSnapshot(content, preamble), BACKUP_FILE_UPDATE_OPTIONS);
}
return updateContentPromise.then(() => model.add(backupResource, versionId));
});
});
}
......
......@@ -16,7 +16,7 @@ import pfs = require('vs/base/node/pfs');
import Uri from 'vs/base/common/uri';
import { BackupFileService, BackupFilesModel } from 'vs/workbench/services/backup/node/backupFileService';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { createTextBufferFactory, TextModel } from 'vs/editor/common/model/textModel';
import { TestContextService, TestTextResourceConfigurationService, getRandomTestPath, TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
......@@ -121,6 +121,56 @@ suite('BackupFileService', () => {
done();
});
});
test('text file (ITextSnapshot)', function (done: () => void) {
const model = TextModel.createFromString('test');
service.backupResource(fooFile, model.createSnapshot()).then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
model.dispose();
done();
});
});
test('untitled file (ITextSnapshot)', function (done: () => void) {
const model = TextModel.createFromString('test');
service.backupResource(untitledFile, model.createSnapshot()).then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.equal(fs.existsSync(untitledBackupPath), true);
assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
model.dispose();
done();
});
});
test('text file (large file, ITextSnapshot)', function (done: () => void) {
const largeString = (new Array(100 * 1024)).join('Large String\n');
const model = TextModel.createFromString(largeString);
service.backupResource(fooFile, model.createSnapshot()).then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\n${largeString}`);
model.dispose();
done();
});
});
test('untitled file (large file, ITextSnapshot)', function (done: () => void) {
const largeString = (new Array(100 * 1024)).join('Large String\n');
const model = TextModel.createFromString(largeString);
service.backupResource(untitledFile, model.createSnapshot()).then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.equal(fs.existsSync(untitledBackupPath), true);
assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\n${largeString}`);
model.dispose();
done();
});
});
});
suite('discardResourceBackup', () => {
......
......@@ -945,26 +945,32 @@ suite('FileService', () => {
fs.readFile(resource.fsPath, (error, data) => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
const model = TextModel.createFromString('Hello Bom');
// Update content: UTF_8 => UTF_8_BOM
_service.updateContent(resource, 'Hello Bom', { encoding: encodingLib.UTF8_with_bom }).done(() => {
_service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8_with_bom }).done(() => {
fs.readFile(resource.fsPath, (error, data) => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), encodingLib.UTF8);
// Update content: PRESERVE BOM when using UTF-8
_service.updateContent(resource, 'Please stay Bom', { encoding: encodingLib.UTF8 }).done(() => {
model.setValue('Please stay Bom');
_service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8 }).done(() => {
fs.readFile(resource.fsPath, (error, data) => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), encodingLib.UTF8);
// Update content: REMOVE BOM
_service.updateContent(resource, 'Go away Bom', { encoding: encodingLib.UTF8, overwriteEncoding: true }).done(() => {
model.setValue('Go away Bom');
_service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8, overwriteEncoding: true }).done(() => {
fs.readFile(resource.fsPath, (error, data) => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
// Update content: BOM comes not back
_service.updateContent(resource, 'Do not come back Bom', { encoding: encodingLib.UTF8 }).done(() => {
model.setValue('Do not come back Bom');
_service.updateContent(resource, model.createSnapshot(), { encoding: encodingLib.UTF8 }).done(() => {
fs.readFile(resource.fsPath, (error, data) => {
assert.equal(encodingLib.detectEncodingByBOMFromBuffer(data, 512), null);
model.dispose();
_service.dispose();
done();
});
......
......@@ -241,7 +241,7 @@ export abstract class TextFileService implements ITextFileService {
private doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): TPromise<void> {
// Handle file resources first
return TPromise.join(dirtyFileModels.map(model => this.backupFileService.backupResource(model.getResource(), model.getValue(), model.getVersionId()))).then(results => {
return TPromise.join(dirtyFileModels.map(model => this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId()))).then(results => {
// Handle untitled resources
const untitledModelPromises = untitledResources
......@@ -250,7 +250,7 @@ export abstract class TextFileService implements ITextFileService {
return TPromise.join(untitledModelPromises).then(untitledModels => {
const untitledBackupPromises = untitledModels.map(model => {
return this.backupFileService.backupResource(model.getResource(), model.getValue(), model.getVersionId());
return this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId());
});
return TPromise.join(untitledBackupPromises).then(() => void 0);
......
......@@ -9,7 +9,7 @@ import URI from 'vs/base/common/uri';
import Event from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, ConfirmResult } from 'vs/workbench/common/editor';
import { IBaseStat, IResolveContentOptions } from 'vs/platform/files/common/files';
import { IBaseStat, IResolveContentOptions, ITextSnapshot } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { ITextBufferFactory } from 'vs/editor/common/model';
......@@ -202,6 +202,8 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
getValue(): string;
createSnapshot(): ITextSnapshot;
isDirty(): boolean;
isResolved(): boolean;
......
......@@ -856,7 +856,7 @@ export class TestBackupFileService implements IBackupFileService {
return null;
}
public backupResource(resource: URI, content: string): TPromise<void> {
public backupResource(resource: URI, content: string | ITextSnapshot): TPromise<void> {
return TPromise.as(void 0);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册