Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vs/base/common/buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export function binaryIndexOf(haystack: Uint8Array, needle: Uint8Array, offset =
}

if (needleLen === 1) {
return haystack.indexOf(needle[0]);
return haystack.indexOf(needle[0], offset);
Comment thread
connor4312 marked this conversation as resolved.
}

if (needleLen > haystackLen - offset) {
Expand Down
29 changes: 27 additions & 2 deletions src/vs/platform/files/browser/indexedDBFileSystemProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ExtUri } from '../../../base/common/resources.js';
import { isString } from '../../../base/common/types.js';
import { URI, UriDto } from '../../../base/common/uri.js';
import { localize } from '../../../nls.js';
import { createFileSystemProviderError, FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from '../common/files.js';
import { createFileSystemProviderError, FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileAppendCapability, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from '../common/files.js';
import { IndexedDB } from '../../../base/browser/indexedDB.js';
import { BroadcastDataChannel } from '../../../base/browser/broadcast.js';

Expand Down Expand Up @@ -152,10 +152,11 @@ class IndexedDBFileSystemNode {
}
}

export class IndexedDBFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
export class IndexedDBFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileAppendCapability {

readonly capabilities: FileSystemProviderCapabilities =
FileSystemProviderCapabilities.FileReadWrite
| FileSystemProviderCapabilities.FileAppend
| FileSystemProviderCapabilities.PathCaseSensitive;
readonly onDidChangeCapabilities: Event<void> = Event.None;

Expand Down Expand Up @@ -263,6 +264,30 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
await this.bulkWrite([[resource, content]]);
}

async appendFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
const existing = await this.stat(resource).catch(() => undefined);
if (existing?.type === FileType.Directory) {
throw ERR_FILE_IS_DIR;
}

// Read existing content and append
let existingContent: Uint8Array;
try {
existingContent = await this.readFile(resource);
} catch (error) {
if (!opts.create) {
throw error;
}
existingContent = new Uint8Array(0);
}

const newContent = new Uint8Array(existingContent.byteLength + content.byteLength);
newContent.set(existingContent, 0);
newContent.set(content, existingContent.byteLength);

await this.bulkWrite([[resource, newContent]]);
}

async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> {
const fileTree = await this.getFiletree();
const fromEntry = fileTree.read(from.path);
Expand Down
8 changes: 7 additions & 1 deletion src/vs/platform/files/common/diskFileSystemProviderClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { newWriteableStream, ReadableStreamEventPayload, ReadableStreamEvents }
import { URI } from '../../../base/common/uri.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IStat, IWatchOptions, IFileSystemProviderError } from './files.js';
import { createFileSystemProviderError, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileAtomicReadOptions, IFileChange, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, IFileSystemProviderError, IFileSystemProviderWithFileAppendCapability, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileWriteOptions, IStat, IWatchOptions } from './files.js';
import { reviveFileChanges } from './watcher.js';

export const LOCAL_FILE_SYSTEM_CHANNEL_NAME = 'localFilesystem';
Expand All @@ -29,6 +29,7 @@ export class DiskFileSystemProviderClient extends Disposable implements
IFileSystemProviderWithFileReadStreamCapability,
IFileSystemProviderWithFileFolderCopyCapability,
IFileSystemProviderWithFileAtomicReadCapability,
IFileSystemProviderWithFileAppendCapability,
IFileSystemProviderWithFileCloneCapability {

constructor(
Expand Down Expand Up @@ -56,6 +57,7 @@ export class DiskFileSystemProviderClient extends Disposable implements
FileSystemProviderCapabilities.FileAtomicRead |
FileSystemProviderCapabilities.FileAtomicWrite |
FileSystemProviderCapabilities.FileAtomicDelete |
FileSystemProviderCapabilities.FileAppend |
FileSystemProviderCapabilities.FileClone |
FileSystemProviderCapabilities.FileRealpath;

Expand Down Expand Up @@ -160,6 +162,10 @@ export class DiskFileSystemProviderClient extends Disposable implements
return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]);
}

appendFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
return this.channel.call('appendFile', [resource, VSBuffer.wrap(content), opts]);
}

open(resource: URI, opts: IFileOpenOptions): Promise<number> {
return this.channel.call('open', [resource, opts]);
}
Expand Down
56 changes: 55 additions & 1 deletion src/vs/platform/files/common/fileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { extUri, extUriIgnorePathCase, IExtUri, isAbsolutePath } from '../../../
import { consumeStream, isReadableBufferedStream, isReadableStream, listenStream, newWriteableStream, peekReadable, peekStream, transform } from '../../../base/common/stream.js';
import { URI } from '../../../base/common/uri.js';
import { localize } from '../../../nls.js';
import { ensureFileSystemProviderError, etag, ETAG_DISABLED, FileChangesEvent, IFileDeleteOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, hasFileAtomicReadCapability, hasFileFolderCopyCapability, hasFileReadStreamCapability, hasOpenReadWriteCloseCapability, hasReadWriteCapability, ICreateFileOptions, IFileContent, IFileService, IFileStat, IFileStatWithMetadata, IFileStreamContent, IFileSystemProvider, IFileSystemProviderActivationEvent, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IReadFileOptions, IReadFileStreamOptions, IResolveFileOptions, IFileStatResult, IFileStatResultWithMetadata, IResolveMetadataFileOptions, IStat, IFileStatWithPartialMetadata, IWatchOptions, IWriteFileOptions, NotModifiedSinceFileOperationError, toFileOperationResult, toFileSystemProviderErrorCode, hasFileCloneCapability, TooLargeFileOperationError, hasFileAtomicDeleteCapability, hasFileAtomicWriteCapability, IWatchOptionsWithCorrelation, IFileSystemWatcher, IWatchOptionsWithoutCorrelation, hasFileRealpathCapability } from './files.js';
import { ensureFileSystemProviderError, etag, ETAG_DISABLED, FileChangesEvent, IFileDeleteOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, hasFileAppendCapability, hasFileAtomicReadCapability, hasFileFolderCopyCapability, hasFileReadStreamCapability, hasOpenReadWriteCloseCapability, hasReadWriteCapability, ICreateFileOptions, IFileContent, IFileService, IFileStat, IFileStatWithMetadata, IFileStreamContent, IFileSystemProvider, IFileSystemProviderActivationEvent, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileWriteOptions, IReadFileOptions, IReadFileStreamOptions, IResolveFileOptions, IFileStatResult, IFileStatResultWithMetadata, IResolveMetadataFileOptions, IStat, IFileStatWithPartialMetadata, IWatchOptions, IWriteFileOptions, NotModifiedSinceFileOperationError, toFileOperationResult, toFileSystemProviderErrorCode, hasFileCloneCapability, TooLargeFileOperationError, hasFileAtomicDeleteCapability, hasFileAtomicWriteCapability, IWatchOptionsWithCorrelation, IFileSystemWatcher, IWatchOptionsWithoutCorrelation, hasFileRealpathCapability } from './files.js';
import { readFileIntoStream } from './io.js';
import { ILogService } from '../../log/common/log.js';
import { ErrorNoTelemetry } from '../../../base/common/errors.js';
Expand Down Expand Up @@ -431,6 +431,60 @@ export class FileService extends Disposable implements IFileService {
return this.resolve(resource, { resolveMetadata: true });
}

async appendFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
Comment thread
connor4312 marked this conversation as resolved.
Outdated
const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource), resource);

try {
// if provider supports append, use it directly
if (hasFileAppendCapability(provider)) {
const buffer = bufferOrReadableOrStream instanceof VSBuffer
? bufferOrReadableOrStream
: isReadableStream(bufferOrReadableOrStream)
? await streamToBuffer(bufferOrReadableOrStream)
: readableToBuffer(bufferOrReadableOrStream);

const writeOptions: IFileWriteOptions = {
create: true,
overwrite: false,
unlock: options?.unlock ?? false,
atomic: options?.atomic ?? false
};

await provider.appendFile(resource, buffer.buffer, writeOptions);

// events
this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.WRITE));
} else {
// fallback: read existing content and write combined content
let existingContent: VSBuffer;
try {
const fileContent = await this.readFile(resource);
existingContent = fileContent.value;
} catch (error) {
// if file doesn't exist, start with empty content
if (toFileOperationResult(error) === FileOperationResult.FILE_NOT_FOUND) {
existingContent = VSBuffer.fromString('');
} else {
throw error;
}
}

const newContent = bufferOrReadableOrStream instanceof VSBuffer
? bufferOrReadableOrStream
: isReadableStream(bufferOrReadableOrStream)
? await streamToBuffer(bufferOrReadableOrStream)
: readableToBuffer(bufferOrReadableOrStream);

const combinedContent = VSBuffer.concat([existingContent, newContent]);
await this.writeFile(resource, combinedContent, options);
Comment thread
connor4312 marked this conversation as resolved.
Outdated
}
} catch (error) {
throw new FileOperationError(localize('err.append', "Unable to append to file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options);
}

return this.resolve(resource, { resolveMetadata: true });
}


private async peekBufferForWriting(provider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise<VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream> {
let peekResult: VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream;
Expand Down
23 changes: 22 additions & 1 deletion src/vs/platform/files/common/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ export interface IFileService {
*/
writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise<IFileStatWithMetadata>;

/**
* Appends content to the end of the file.
*
* Emits a `FileOperation.WRITE` file operation event when successful.
*/
appendFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise<IFileStatWithMetadata>;

/**
* Moves the file/folder to a new path identified by the resource.
*
Expand Down Expand Up @@ -654,7 +661,12 @@ export const enum FileSystemProviderCapabilities {
/**
* Provider support to resolve real paths.
*/
FileRealpath = 1 << 18
FileRealpath = 1 << 18,

/**
* Provider support to append to files.
*/
FileAppend = 1 << 19
}

export interface IFileSystemProvider {
Expand All @@ -676,6 +688,7 @@ export interface IFileSystemProvider {

readFile?(resource: URI): Promise<Uint8Array>;
writeFile?(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void>;
appendFile?(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void>;

readFileStream?(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array>;

Expand All @@ -696,6 +709,14 @@ export function hasReadWriteCapability(provider: IFileSystemProvider): provider
return !!(provider.capabilities & FileSystemProviderCapabilities.FileReadWrite);
}

export interface IFileSystemProviderWithFileAppendCapability extends IFileSystemProvider {
appendFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void>;
}

export function hasFileAppendCapability(provider: IFileSystemProvider): provider is IFileSystemProviderWithFileAppendCapability {
return !!(provider.capabilities & FileSystemProviderCapabilities.FileAppend);
}

export interface IFileSystemProviderWithFileFolderCopyCapability extends IFileSystemProvider {
copy(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void>;
}
Expand Down
37 changes: 33 additions & 4 deletions src/vs/platform/files/common/inMemoryFilesystemProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
import * as resources from '../../../base/common/resources.js';
import { ReadableStreamEvents, newWriteableStream } from '../../../base/common/stream.js';
import { URI } from '../../../base/common/uri.js';
import { FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions, createFileSystemProviderError, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileOpenOptions, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileReadStreamCapability } from './files.js';
import { FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileAppendCapability, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions, createFileSystemProviderError, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileOpenOptions, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileReadStreamCapability } from './files.js';

class File implements IStat {

Expand Down Expand Up @@ -56,6 +56,7 @@ export class InMemoryFileSystemProvider extends Disposable implements
IFileSystemProviderWithFileReadWriteCapability,
IFileSystemProviderWithOpenReadWriteCloseCapability,
IFileSystemProviderWithFileReadStreamCapability,
IFileSystemProviderWithFileAppendCapability,
IFileSystemProviderWithFileAtomicReadCapability,
IFileSystemProviderWithFileAtomicWriteCapability,
IFileSystemProviderWithFileAtomicDeleteCapability {
Expand All @@ -65,14 +66,13 @@ export class InMemoryFileSystemProvider extends Disposable implements
private _onDidChangeCapabilities = this._register(new Emitter<void>());
readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;

private _capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;
private _capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileAppend | FileSystemProviderCapabilities.PathCaseSensitive;
get capabilities(): FileSystemProviderCapabilities { return this._capabilities; }

setReadOnly(readonly: boolean) {
const isReadonly = !!(this._capabilities & FileSystemProviderCapabilities.Readonly);
if (readonly !== isReadonly) {
this._capabilities = readonly ? FileSystemProviderCapabilities.Readonly | FileSystemProviderCapabilities.PathCaseSensitive | FileSystemProviderCapabilities.FileReadWrite
: FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;
this._capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileAppend | FileSystemProviderCapabilities.PathCaseSensitive | (readonly ? FileSystemProviderCapabilities.Readonly : 0);
this._onDidChangeCapabilities.fire();
}
}
Expand Down Expand Up @@ -136,6 +136,35 @@ export class InMemoryFileSystemProvider extends Disposable implements
this._fireSoon({ type: FileChangeType.UPDATED, resource });
}

async appendFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
const basename = resources.basename(resource);
const parent = this._lookupParentDirectory(resource);
let entry = parent.entries.get(basename);
if (entry instanceof Directory) {
throw createFileSystemProviderError('file is directory', FileSystemProviderErrorCode.FileIsADirectory);
}
if (!entry && !opts.create) {
throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound);
}
if (!entry) {
entry = new File(basename);
parent.entries.set(basename, entry);
this._fireSoon({ type: FileChangeType.ADDED, resource });
}

// Append to existing data
const existingData = entry.data || new Uint8Array(0);
const newData = new Uint8Array(existingData.byteLength + content.byteLength);
newData.set(existingData, 0);
newData.set(content, existingData.byteLength);

entry.mtime = Date.now();
entry.size = newData.byteLength;
entry.data = newData;

this._fireSoon({ type: FileChangeType.UPDATED, resource });
}

// file open/read/write/close
open(resource: URI, opts: IFileOpenOptions): Promise<number> {
const data = this._lookupAsFile(resource, false).data;
Expand Down
Loading
Loading