files - cleanup & remove obsolete importFile action

This commit is contained in:
Benjamin Pasero 2018-04-12 07:53:07 +02:00
parent fe9d9532ea
commit 84ce87420b
8 changed files with 81 additions and 204 deletions

View file

@ -125,11 +125,6 @@ export interface IFileService {
*/
del(resource: URI, useTrash?: boolean): TPromise<void>;
/**
* Imports the file to the parent identified by the resource.
*/
importFile(source: URI, targetFolder: URI): TPromise<IImportResult>;
/**
* Allows to start a watcher that reports file change events on the provided resource.
*/
@ -178,8 +173,7 @@ export enum FileOperation {
CREATE,
DELETE,
MOVE,
COPY,
IMPORT
COPY
}
export class FileOperationEvent {
@ -546,11 +540,6 @@ export interface ICreateFileOptions {
overwrite?: boolean;
}
export interface IImportResult {
stat: IFileStat;
isNew: boolean;
}
export class FileOperationError extends Error {
constructor(message: string, public fileOperationResult: FileOperationResult, public options?: IResolveContentOptions & IUpdateContentOptions & ICreateFileOptions) {
super(message);

View file

@ -13,6 +13,7 @@ import { sequence, ITask, always } from 'vs/base/common/async';
import * as paths from 'vs/base/common/paths';
import * as resources from 'vs/base/common/resources';
import URI from 'vs/base/common/uri';
import { posix } from 'path';
import * as errors from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import * as strings from 'vs/base/common/strings';
@ -725,8 +726,8 @@ class BaseDeleteFileAction extends BaseFileAction {
}
}
/* Import File */
export class ImportFileAction extends BaseFileAction {
/* Add File */
export class AddFilesAction extends BaseFileAction {
private tree: ITree;
@ -740,7 +741,7 @@ export class ImportFileAction extends BaseFileAction {
@INotificationService notificationService: INotificationService,
@ITextFileService textFileService: ITextFileService
) {
super('workbench.files.action.importFile', nls.localize('importFiles', "Import Files"), fileService, notificationService, textFileService);
super('workbench.files.action.addFile', nls.localize('addFiles', "Add Files"), fileService, notificationService, textFileService);
this.tree = tree;
this.element = element;
@ -753,10 +754,10 @@ export class ImportFileAction extends BaseFileAction {
}
public run(resources: URI[]): TPromise<any> {
const importPromise = TPromise.as(null).then(() => {
const addPromise = TPromise.as(null).then(() => {
if (resources && resources.length > 0) {
// Find parent for import
// Find parent to add to
let targetElement: ExplorerItem;
if (this.element) {
targetElement = this.element;
@ -797,15 +798,15 @@ export class ImportFileAction extends BaseFileAction {
return void 0;
}
// Run import in sequence
const importPromisesFactory: ITask<TPromise<void>>[] = [];
// Run add in sequence
const addPromisesFactory: ITask<TPromise<void>>[] = [];
resources.forEach(resource => {
importPromisesFactory.push(() => {
addPromisesFactory.push(() => {
const sourceFile = resource;
const targetFile = targetElement.resource.with({ path: paths.join(targetElement.resource.path, paths.basename(sourceFile.path)) });
// if the target exists and is dirty, make sure to revert it. otherwise the dirty contents
// of the target file would replace the contents of the imported file. since we already
// of the target file would replace the contents of the added file. since we already
// confirmed the overwrite before, this is OK.
let revertPromise = TPromise.wrap(null);
if (this.textFileService.isDirty(targetFile)) {
@ -813,18 +814,19 @@ export class ImportFileAction extends BaseFileAction {
}
return revertPromise.then(() => {
return this.fileService.importFile(sourceFile, targetElement.resource).then(res => {
const target = targetElement.resource.with({ path: posix.join(targetElement.resource.path, posix.basename(sourceFile.path)) });
return this.fileService.copyFile(sourceFile, target, true).then(stat => {
// if we only import one file, just open it directly
// if we only add one file, just open it directly
if (resources.length === 1) {
this.editorService.openEditor({ resource: res.stat.resource, options: { pinned: true } }).done(null, errors.onUnexpectedError);
this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }).done(null, errors.onUnexpectedError);
}
}, error => this.onError(error));
});
});
});
return sequence(importPromisesFactory);
return sequence(addPromisesFactory);
});
});
}
@ -832,7 +834,7 @@ export class ImportFileAction extends BaseFileAction {
return void 0;
});
return importPromise.then(() => {
return addPromise.then(() => {
this.tree.clearHighlight();
}, (error: any) => {
this.onError(error);

View file

@ -470,7 +470,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
}
// Add
if (e.operation === FileOperation.CREATE || e.operation === FileOperation.IMPORT || e.operation === FileOperation.COPY) {
if (e.operation === FileOperation.CREATE || e.operation === FileOperation.COPY) {
const addedElement = e.target;
const parentResource = resources.dirname(addedElement.resource);
const parents = this.model.findAll(parentResource);

View file

@ -24,7 +24,7 @@ import { IFilesConfiguration, SortOrder } from 'vs/workbench/parts/files/common/
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { FileOperationError, FileOperationResult, IFileService, FileKind } from 'vs/platform/files/common/files';
import { ResourceMap } from 'vs/base/common/map';
import { DuplicateFileAction, ImportFileAction, IEditableData, IFileViewletState, FileCopiedContext } from 'vs/workbench/parts/files/electron-browser/fileActions';
import { DuplicateFileAction, AddFilesAction, IEditableData, IFileViewletState, FileCopiedContext } from 'vs/workbench/parts/files/electron-browser/fileActions';
import { IDataSource, ITree, IAccessibilityProvider, IRenderer, ContextMenuEvent, ISorter, IFilter, IDragAndDropData, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY, DRAG_OVER_ACCEPT_BUBBLE_UP, DRAG_OVER_ACCEPT_BUBBLE_UP_COPY, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
import { DesktopDragAndDropData, ExternalElementsDragAndDropData } from 'vs/base/parts/tree/browser/treeDnd';
import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
@ -921,9 +921,9 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop {
// Handle dropped files (only support FileStat as target)
else if (target instanceof ExplorerItem) {
const importAction = this.instantiationService.createInstance(ImportFileAction, tree, target, null);
const addFilesAction = this.instantiationService.createInstance(AddFilesAction, tree, target, null);
return importAction.run(droppedResources.map(res => res.resource));
return addFilesAction.run(droppedResources.map(res => res.resource));
}
return void 0;

View file

@ -10,7 +10,7 @@ import * as fs from 'fs';
import * as os from 'os';
import * as crypto from 'crypto';
import * as assert from 'assert';
import { isParent, FileOperation, FileOperationEvent, IContent, IFileService, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangeType, IImportResult, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration } from 'vs/platform/files/common/files';
import { isParent, FileOperation, FileOperationEvent, IContent, IFileService, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangeType, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration } from 'vs/platform/files/common/files';
import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/files';
import { isEqualOrParent } from 'vs/base/common/paths';
import { ResourceMap } from 'vs/base/common/map';
@ -47,6 +47,42 @@ import product from 'vs/platform/node/product';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { shell } from 'electron';
class BufferPool {
static _64K = new BufferPool(64 * 1024, 5);
constructor(
readonly bufferSize: number,
private readonly _capacity: number,
private readonly _free: Buffer[] = [],
) { }
acquire(): Buffer {
if (this._free.length === 0) {
return Buffer.allocUnsafe(this.bufferSize);
} else {
return this._free.shift();
}
}
release(buf: Buffer): void {
if (this._free.length <= this._capacity) {
this._free.push(buf);
}
}
}
export interface IEncodingOverride {
parent?: uri;
extension?: string;
encoding: string;
}
export interface IFileServiceTestOptions {
disableWatcher?: boolean;
encodingOverride?: IEncodingOverride[];
}
export class FileService implements IFileService {
public _serviceBrand: any;
@ -191,12 +227,6 @@ export class FileService implements IFileService {
return this._onAfterOperation.event;
}
public updateOptions(options: IFileServiceTestOptions): void {
if (options) {
objects.mixin(this.options, options); // overwrite current options
}
}
private setupFileWatching(): void {
// dispose old if any
@ -213,10 +243,11 @@ export class FileService implements IFileService {
// new watcher: use it if setting tells us so or we run in multi-root environment
const configuration = this.configurationService.getValue<IFilesConfiguration>();
if ((configuration.files && configuration.files.useExperimentalFileWatcher) || workbenchState === WorkbenchState.WORKSPACE) {
this.activeWorkspaceFileChangeWatcher = toDisposable(this.setupNsfwWorkspaceWatching().startWatching());
const multiRootWatcher = new NsfwWatcherService(this.contextService, this.configurationService, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
this.activeWorkspaceFileChangeWatcher = toDisposable(multiRootWatcher.startWatching());
}
// old watcher
// legacy watcher
else {
let watcherIgnoredPatterns: string[] = [];
if (configuration.files && configuration.files.watcherExclude) {
@ -224,25 +255,15 @@ export class FileService implements IFileService {
}
if (isWindows) {
this.activeWorkspaceFileChangeWatcher = toDisposable(this.setupWin32WorkspaceWatching(watcherIgnoredPatterns).startWatching());
const legacyWindowsWatcher = new WindowsWatcherService(this.contextService, watcherIgnoredPatterns, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
this.activeWorkspaceFileChangeWatcher = toDisposable(legacyWindowsWatcher.startWatching());
} else {
this.activeWorkspaceFileChangeWatcher = toDisposable(this.setupUnixWorkspaceWatching(watcherIgnoredPatterns).startWatching());
const legacyUnixWatcher = new UnixWatcherService(this.contextService, watcherIgnoredPatterns, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
this.activeWorkspaceFileChangeWatcher = toDisposable(legacyUnixWatcher.startWatching());
}
}
}
private setupWin32WorkspaceWatching(watcherIgnoredPatterns: string[]): WindowsWatcherService {
return new WindowsWatcherService(this.contextService, watcherIgnoredPatterns, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
}
private setupUnixWorkspaceWatching(watcherIgnoredPatterns: string[]): UnixWatcherService {
return new UnixWatcherService(this.contextService, watcherIgnoredPatterns, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
}
private setupNsfwWorkspaceWatching(): NsfwWatcherService {
return new NsfwWatcherService(this.contextService, this.configurationService, e => this._onFileChanges.fire(e), err => this.handleError(err), this.environmentService.verbose);
}
public resolveFile(resource: uri, options?: IResolveFileOptions): TPromise<IFileStat> {
return this.resolve(resource, options);
}
@ -512,9 +533,10 @@ export class FileService implements IFileService {
} else {
// when receiving the first chunk of data we need to create the
// decoding stream which is then used to drive the string stream.
const autoGuessEncoding = (options && options.autoGuessEncoding) || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding');
TPromise.as(encoding.detectEncodingFromBuffer(
{ buffer: chunkBuffer, bytesRead },
options && options.autoGuessEncoding || this.configuredAutoGuessEncoding(resource)
autoGuessEncoding
)).then(detected => {
if (options && options.acceptTextOnly && detected.seemsBinary) {
@ -920,32 +942,6 @@ export class FileService implements IFileService {
});
}
public importFile(source: uri, targetFolder: uri): TPromise<IImportResult> {
const sourcePath = this.toAbsolutePath(source);
const targetResource = uri.file(paths.join(targetFolder.fsPath, paths.basename(source.fsPath)));
const targetPath = this.toAbsolutePath(targetResource);
// 1.) resolve
return pfs.stat(sourcePath).then(stat => {
if (stat.isDirectory()) {
return TPromise.wrapError<IImportResult>(new Error(nls.localize('foldersCopyError', "Folders cannot be copied into the workspace. Please select individual files to copy them."))); // for now we do not allow to import a folder into a workspace
}
// 2.) copy
return this.doMoveOrCopyFile(sourcePath, targetPath, true, true).then(exists => {
// 3.) resolve
return this.resolve(targetResource).then(stat => {
// Events
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.IMPORT, stat));
return <IImportResult>{ isNew: !exists, stat };
});
});
});
}
public del(resource: uri, useTrash?: boolean): TPromise<void> {
if (useTrash) {
return this.doMoveItemToTrash(resource);
@ -992,8 +988,7 @@ export class FileService implements IFileService {
}
private resolve(resource: uri, options: IResolveFileOptions = Object.create(null)): TPromise<IFileStat> {
return this.toStatResolver(resource)
.then(model => model.resolve(options));
return this.toStatResolver(resource).then(model => model.resolve(options));
}
private toStatResolver(resource: uri): TPromise<StatResolver> {
@ -1043,10 +1038,6 @@ export class FileService implements IFileService {
return fileEncoding;
}
private configuredAutoGuessEncoding(resource: uri): boolean {
return this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding');
}
private configuredEncoding(resource: uri): string {
return this.textResourceConfigurationService.getValue(resource, 'files.encoding');
}
@ -1181,17 +1172,6 @@ export class FileService implements IFileService {
}
}
export interface IEncodingOverride {
parent?: uri;
extension?: string;
encoding: string;
}
export interface IFileServiceTestOptions {
disableWatcher?: boolean;
encodingOverride?: IEncodingOverride[];
}
function etag(stat: fs.Stats): string;
function etag(size: number, mtime: number): string;
function etag(arg1: any, arg2?: any): string {
@ -1208,33 +1188,6 @@ function etag(arg1: any, arg2?: any): string {
return `"${crypto.createHash('sha1').update(String(size) + String(mtime)).digest('hex')}"`;
}
class BufferPool {
static _64K = new BufferPool(64 * 1024, 5);
constructor(
readonly bufferSize: number,
private readonly _capacity: number,
private readonly _free: Buffer[] = [],
) {
//
}
acquire(): Buffer {
if (this._free.length === 0) {
return Buffer.allocUnsafe(this.bufferSize);
} else {
return this._free.shift();
}
}
release(buf: Buffer): void {
if (this._free.length <= this._capacity) {
this._free.push(buf);
}
}
}
export class StatResolver {
private name: string;
private etag: string;

View file

@ -6,7 +6,7 @@
import URI from 'vs/base/common/uri';
import { FileService } from 'vs/workbench/services/files/electron-browser/fileService';
import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, IResolveFileOptions, IResolveFileResult, FileOperationEvent, FileOperation, IFileSystemProvider, IStat, FileType2, IImportResult, FileChangesEvent, ICreateFileOptions, FileOperationError, FileOperationResult, ITextSnapshot, snapshotToString } from 'vs/platform/files/common/files';
import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, IResolveFileOptions, IResolveFileResult, FileOperationEvent, FileOperation, IFileSystemProvider, IStat, FileType2, FileChangesEvent, ICreateFileOptions, FileOperationError, FileOperationResult, ITextSnapshot, snapshotToString } from 'vs/platform/files/common/files';
import { TPromise } from 'vs/base/common/winjs.base';
import { posix } from 'path';
import { IDisposable } from 'vs/base/common/lifecycle';
@ -430,15 +430,6 @@ export class RemoteFileService extends FileService {
});
}
importFile(source: URI, targetFolder: URI): TPromise<IImportResult> {
if (source.scheme === targetFolder.scheme && source.scheme === Schemas.file) {
return super.importFile(source, targetFolder);
} else {
const target = targetFolder.with({ path: posix.join(targetFolder.path, posix.basename(source.path)) });
return this.copyFile(source, target, false).then(stat => ({ stat, isNew: false }));
}
}
copyFile(source: URI, target: URI, overwrite?: boolean): TPromise<IFileStat> {
if (source.scheme === target.scheme && source.scheme === Schemas.file) {
return super.copyFile(source, target, overwrite);

View file

@ -426,36 +426,18 @@ suite('FileService', () => {
});
});
test('importFile', function () {
let event: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
event = e;
});
return service.resolveFile(uri.file(path.join(testDir, 'deep'))).then(target => {
const resource = uri.file(require.toUrl('./fixtures/service/index.html'));
return service.importFile(resource, target.resource).then(res => {
assert.equal(res.isNew, true);
assert.equal(fs.existsSync(res.stat.resource.fsPath), true);
assert.ok(event);
assert.equal(event.resource.fsPath, resource.fsPath);
assert.equal(event.operation, FileOperation.IMPORT);
assert.equal(event.target.resource.fsPath, res.stat.resource.fsPath);
toDispose.dispose();
});
});
});
test('importFile - MIX CASE', function () {
test('copyFile - MIX CASE', function () {
return service.resolveFile(uri.file(path.join(testDir, 'index.html'))).then(source => {
return service.rename(source.resource, 'CONWAY.js').then(renamed => { // index.html => CONWAY.js
assert.equal(fs.existsSync(renamed.resource.fsPath), true);
assert.ok(fs.readdirSync(testDir).some(f => f === 'CONWAY.js'));
return service.resolveFile(uri.file(path.join(testDir, 'deep', 'conway.js'))).then(source => {
return service.importFile(source.resource, uri.file(testDir)).then(res => { // CONWAY.js => conway.js
assert.equal(fs.existsSync(res.stat.resource.fsPath), true);
const targetParent = uri.file(testDir);
const target = targetParent.with({ path: path.posix.join(targetParent.path, path.posix.basename(source.resource.path)) });
return service.copyFile(source.resource, target, true).then(res => { // CONWAY.js => conway.js
assert.equal(fs.existsSync(res.resource.fsPath), true);
assert.ok(fs.readdirSync(testDir).some(f => f === 'conway.js'));
});
});
@ -463,48 +445,12 @@ suite('FileService', () => {
});
});
test('importFile - overwrite folder with file', function () {
let createEvent: FileOperationEvent;
let importEvent: FileOperationEvent;
let deleteEvent: FileOperationEvent;
const toDispose = service.onAfterOperation(e => {
if (e.operation === FileOperation.CREATE) {
createEvent = e;
} else if (e.operation === FileOperation.DELETE) {
deleteEvent = e;
} else if (e.operation === FileOperation.IMPORT) {
importEvent = e;
}
});
return service.resolveFile(uri.file(testDir)).then(parent => {
const folderResource = uri.file(path.join(parent.resource.fsPath, 'conway.js'));
return service.createFolder(folderResource).then(f => {
const resource = uri.file(path.join(testDir, 'deep', 'conway.js'));
return service.importFile(resource, uri.file(testDir)).then(res => {
assert.equal(fs.existsSync(res.stat.resource.fsPath), true);
assert.ok(fs.readdirSync(testDir).some(f => f === 'conway.js'));
assert.ok(fs.statSync(res.stat.resource.fsPath).isFile);
assert.ok(createEvent);
assert.ok(deleteEvent);
assert.ok(importEvent);
assert.equal(importEvent.resource.fsPath, resource.fsPath);
assert.equal(importEvent.target.resource.fsPath, res.stat.resource.fsPath);
assert.equal(deleteEvent.resource.fsPath, folderResource.fsPath);
toDispose.dispose();
});
});
});
});
test('importFile - same file', function () {
test('copyFile - same file', function () {
return service.resolveFile(uri.file(path.join(testDir, 'index.html'))).then(source => {
return service.importFile(source.resource, uri.file(path.dirname(source.resource.fsPath))).then(imported => {
assert.equal(imported.stat.size, source.size);
const targetParent = uri.file(path.dirname(source.resource.fsPath));
const target = targetParent.with({ path: path.posix.join(targetParent.path, path.posix.basename(source.resource.path)) });
return service.copyFile(source.resource, target, true).then(copied => {
assert.equal(copied.size, source.size);
});
});
});

View file

@ -32,7 +32,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IEditorGroupService, GroupArrangement, GroupOrientation, IEditorTabOptions, IMoveOptions } from 'vs/workbench/services/group/common/groupService';
import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService';
import { FileOperationEvent, IFileService, IResolveContentOptions, FileOperationError, IFileStat, IResolveFileResult, IImportResult, FileChangesEvent, IResolveFileOptions, IContent, IUpdateContentOptions, IStreamContent, ICreateFileOptions, ITextSnapshot } from 'vs/platform/files/common/files';
import { FileOperationEvent, IFileService, IResolveContentOptions, FileOperationError, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, IContent, IUpdateContentOptions, IStreamContent, ICreateFileOptions, ITextSnapshot } from 'vs/platform/files/common/files';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
@ -805,10 +805,6 @@ export class TestFileService implements IFileService {
return TPromise.as(null);
}
importFile(source: URI, targetFolder: URI): TPromise<IImportResult> {
return TPromise.as(null);
}
watchFileChanges(resource: URI): void {
}